Mockito 说实际调用在使用 MutableLiveData 值时有不同的参数

Mockito Says Actual invocation has different arguments when using MutableLiveData Value

请阅读整个描述我已尽力解释代码的每个角落。

Mockito 说实际调用有不同的参数 我正在测试一个更新密码函数,它有这样的代码,

fun update() {
        if (uiModel.validateData().isEmpty()) {
            changePassword(
                uiModel.oldpassword.value ?: "",
                uiModel.newpassword.value ?: "",
                uiModel.confirmpassword.value ?: ""
            )
        } else {
            showToast(uiModel.validateData()[0])
        }
    } 

uiModel.validateData().isEmpty() 此行验证用户输入是否正确,然后在块内部继续。 uiModel.oldpassword.value uiModel 是我的支持 class,它有一个 Mutablelive 数据,它通过数据绑定连接到 EditTextView,后者获取和设置数据到视图中。我已经嘲笑了 uiModel class 这是 dagger 在 vi​​ewModel 构造函数中提供的简单 class 这里是 uiModel class 代码。

class ChangePasswordUiModel @Inject constructor() {
   var oldpassword = MutableLiveData<String>().default("")
   var newpassword= MutableLiveData<String>().default("")
   var confirmpassword= MutableLiveData<String>().default("")
}

这里是 viewModel 注入设置

@ChangePasswordScope
class ChangePasswordViewModel @Inject constructor(
    private val useCase: ChangePasswordUseCase,
    val uiModel: ChangePasswordUiModel
) : BaseFragmentViewModel() {
}

如果所有验证都已设置,则会调用此函数。

changePassword(uiModel.oldpassword.value ?: "",
                uiModel.newpassword.value ?: "",
                uiModel.confirmpassword.value ?: ""
            )

实际上是这样的。

    var testValue = ""
    private fun changePassword(oldpass: String, newpass: String, confirmPass: String) {
        viewModelScope.launch {
                useCase.changePassword(
                    oldpass,
                    newpass,
                    confirmPass
                ).let { result ->
                    when (result) {
                        is Result.Success -> {
                            testValue = "Success"
                        }
                        is Result.Exception -> showException(result.exception)
                        is Result.Error -> showError(parseError(result.errorBody))
                        else -> {

                        }
                    }
                }
        }
    }

现在 useCase.changePassword() 这个函数对我来说真的很神奇,它实际上发起了网络请求,return 我是一个自定义密封的 class,它具有三个值 Success(Any()),Error (), 例外。 用例看起来像这样。

interface ChangePasswordUseCase {
    suspend fun changePassword(oldpassword: String, newpassword: String, confirmpassword: String): Result
}

现在是测试的问题

我想在 update() 函数调用后检查是否调用了 changepassword 我的测试代码是这样的,

// these are the values which set up in  @before 
    val useCase = mock<ChangePasswordUseCase>()
    val uiModel = mock<ChangePasswordUiModel>()  
    val SUT: ChangePasswordViewModel by lazy { ChangePasswordViewModel(useCase, uiModel) }

 @Test
    fun `update pass validate pass and change pass`() {
        val emptyLiveData = MutableLiveData("abc")
        whenever(uiModel.validateData()).thenReturn(mutableListOf())
        whenever(uiModel.oldpassword).thenReturn(emptyLiveData)
        whenever(uiModel.confirmpassword).thenReturn(emptyLiveData)
        whenever(uiModel.newpassword).thenReturn(emptyLiveData)
 
        runBlockingTest {
            whenever(
                useCase.changePassword(
                    (emptyLiveData.value!!),
                    (emptyLiveData.value!!),
                    (emptyLiveData.value!!)
                )
            ).thenReturn(
                Result.Success(
                    ChangePasswordResponse()
                )
            )
            SUT.update()

            verify(useCase).changePassword(
                 (emptyLiveData.value!!),
               (emptyLiveData.value!!),
               (emptyLiveData.value!!)
            )

            assertThat(SUT.testValue).isEqualTo("Success")
      }
   }

whenever是我写在Mockito.when

上的扩展函数

最后这个问题花了我一天时间但没有解决...我知道这个问题是价值观参考问题,但我不知道如何解决这个问题

错误

Argument(s) are different! Wanted:
changePasswordUseCase.changePassword(
    "abc",
    "abc",
    "abc",
    Continuation at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass.invokeSuspend(ChangePasswordViewModelTest.kt:105)
);
-> at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass.invokeSuspend(ChangePasswordViewModelTest.kt:102)
Actual invocation has different arguments:
changePasswordUseCase.changePassword(
    "abc",
    "abc",
    "abc",
    Continuation at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModel$changePassword.invokeSuspend(ChangePasswordViewModel.kt:61)
);
-> at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModel$changePassword.invokeSuspend(ChangePasswordViewModel.kt:58)

Comparison Failure: 
<Click to see difference>

Argument(s) are different! Wanted:
changePasswordUseCase.changePassword(
    "abc",
    "abc",
    "abc",
    Continuation at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass.invokeSuspend(ChangePasswordViewModelTest.kt:105)
);
-> at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass.invokeSuspend(ChangePasswordViewModelTest.kt:102)
Actual invocation has different arguments:
changePasswordUseCase.changePassword(
    "abc",
    "abc",
    "abc",
    Continuation at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModel$changePassword.invokeSuspend(ChangePasswordViewModel.kt:61)
);
-> at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModel$changePassword.invokeSuspend(ChangePasswordViewModel.kt:58)

    at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass.invokeSuspend(ChangePasswordViewModelTest.kt:102)
    at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass.invoke(ChangePasswordViewModelTest.kt)
    at kotlinx.coroutines.test.TestBuildersKt$runBlockingTest$deferred.invokeSuspend(TestBuilders.kt:50)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.test.TestCoroutineDispatcher.dispatch(TestCoroutineDispatcher.kt:50)
    at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:305)
    at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
    at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:27)
    at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
    at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
    at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:91)
    at kotlinx.coroutines.BuildersKt.async(Unknown Source)
    at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:84)
    at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:49)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest$default(TestBuilders.kt:45)
    at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest.update pass validate pass and change pass(ChangePasswordViewModelTest.kt:88)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.rules.TestWatcher.evaluate(TestWatcher.java:61)
    at org.junit.rules.TestWatcher.evaluate(TestWatcher.java:61)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access0(ParentRunner.java:66)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:64)


Process finished with exit code -1

你的代码看起来很完美。

使用协程和挂起函数时,您将需要协程测试和 mockito-kotlin 依赖项。
确保在 build.gralde.

中添加了两个依赖项
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"