kalinor
kalinor

Reputation: 189

How to fix this ClassCastException in UnitTest

I'm currently facing some problems while adding UnitTests to my JetPack Compose Project.

I want to test the following (a bit shortened) function in my viewmodel:

    fun onPressSignUp() {
        viewModelScope.launch(Dispatchers.IO) {
            val email = signupScreenState.value.userEmail
            val password = signupScreenState.value.password

            authRepository.signUp(email = email, password = password).onSuccess {
               // do stuff
            }.onFailure { e ->
                Log.e("SignUpScreen", "onPressSignUp: Failure", e)
               // do other stuff
            }
        }
    }

The authRepository.signUp function looks like this:

    override suspend fun signUp(email: String, password: String): Result<Unit> {
        try {
            auth.createUserWithEmailAndPassword(email, password).await()
            return Result.success(Unit)
        } catch (e: Exception) {
            Log.e("Firebase Authentication", "signUpUserWithEmail:failure")
            return Result.failure(e)
        }
    }

And here is my test, which currently fails:

    class SignupScreenViewModelTest {
    
        private lateinit var authRepoMock: AuthenticationRepository
        private lateinit var signupScreenViewModel: SignupScreenViewModel
    
        @Before
        fun setup() {
            authRepoMock = mockk()
            signupScreenViewModel = SignupScreenViewModel(authRepoMock)
        }
    
        @Test
        fun onPressSignUpFailure() {
    
            coEvery { authRepoMock.signUp(any(), any()) } returns Result.failure(Exception())
    
            signupScreenViewModel.onPressSignUp()
    
            coVerify { authRepoMock.signUp(any(), any()) }
    
            assertTrue(signupScreenViewModel.signupScreenState.value.hasError)
        }
    }

So basically I wanted the authRepoMock to return a Result, which makes the ViewModel-function onPressSignUp do some stuff and set the viewmodel state so hasError is true. But instead I get the following error:

Exception in thread "DefaultDispatcher-worker-1 @coroutine#3" java.lang.ClassCastException: class kotlin.Result cannot be cast to class kotlin.Unit (kotlin.Result and kotlin.Unit are in unnamed module of loader 'app') at com.packagename.signup.SignupScreenViewModel$onPressSignUp$1.invokeSuspend(SignupScreenViewModel.kt:44) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100) at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:113) at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)

I also get an AssertionError as the VieModelState does not change because of the ClassCastException.

Is there any way to fix this error without changing any implementation details in my viewmodel or repository?

Upvotes: 1

Views: 33

Answers (1)

Krzysztof S.
Krzysztof S.

Reputation: 181

A common workaround is to use an answer block (i.e. coAnswers) so that the result is generated at call time and correctly typed. For example, change your mock to:

coEvery { authRepoMock.signUp(any(), any()) } coAnswers { Result.failure(Exception()) }

This makes sure that the suspend function returns a Result<Unit> as expected and avoids the inline class unboxing issue.

Note: You might also consider using a TestCoroutineDispatcher (and setting Dispatchers appropriately) so your coroutine code runs in a controlled test environment, but the key fix for this error is using coAnswers (or casting explicitly) rather than returns.

This workaround fixes the error without any changes to your ViewModel or repository implementation.

Upvotes: 0

Related Questions