简体   繁体   English

Android Kotlin 单元测试因 io.mockk.MockKException 失败:找不到答案

[英]Android Kotlin Unit test failing with io.mockk.MockKException: no answer found for

EDIT: For future readers.编辑:对于未来的读者。 I don't know if this question will help you very much.The logic of the fun has changed drastically so I am closing the question, but won't delete it.不知道这个问题对你有没有很大的帮助。乐趣的逻辑已经发生了巨大的变化,所以我关闭了这个问题,但不会删除它。

I am trying to write some unit tests for my ViewModel.我正在尝试为我的 ViewModel 编写一些单元测试。 I am using Mockk and Junit5.我正在使用 Mockk 和 Junit5。

What should happen: The mockked repository returns fakeresponse, I call the fun in the VM, it sets the livedata to be the fake response data.应该发生什么:模拟存储库返回 fakeresponse,我在 VM 中调用 fun,它将 livedata 设置为伪响应数据。

What actually happens:实际发生的情况:

Exception in thread "DefaultDispatcher-worker-2 @coroutine#1" io.mockk.MockKException: no answer found for: DrillRepository(#1).loadDrillTypes()
    at io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:90)
    at io.mockk.impl.stub.MockKStub.answer(MockKStub.kt:42)
    at io.mockk.impl.recording.states.AnsweringState.call(AnsweringState.kt:16)
    at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
    at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:263)
    at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:25)
    at io.mockk.proxy.jvm.advice.Interceptor.call(Interceptor.kt:20)
    at com.nikolam.basketpro.data.DrillRepository.loadDrillTypes(DrillRepository.kt:11)
    at com.nikolam.basketpro.ui.drills.selection.DrillsSelectionViewModel$fetchDrillTypes$1.invokeSuspend(DrillsSelectionViewModel.kt:41)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)

org.opentest4j.AssertionFailedError: 
Expected :[DrillsType(drillType_title=title1, drillType_imageUrl=url1), DrillsType(drillType_title=title2, drillType_imageUrl=url2)]
Actual   :null
<Click to see difference>


    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
    at org.junit.jupiter.api.AssertionUtils.failNotEqual(AssertionUtils.java:62)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:182)
    at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:177)
    at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:1124)
    at com.nikolam.basketpro.ui.drills.selection.DrillsSelectionViewModelTest.drillTypeListWillBePopulatedOnSuccess(DrillsSelectionViewModelTest.kt:43)
    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.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:205)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:201)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

This is the test这是测试

@ExtendWith(value = [InstantExecutorExtension::class, TestSchedulerExtension::class])
internal class DrillsSelectionViewModelTest{

    val mockkDrillRepository = mockk<DrillRepository>()

    lateinit var drillsSelectionViewModel : DrillsSelectionViewModel

    @BeforeEach
    fun setup(){

        drillsSelectionViewModel = DrillsSelectionViewModel(mockkDrillRepository)

        val fakeResponse = Observable.just(DrillsType("title1", "url1"), DrillsType("title2","url2"))


        every{ mockkDrillRepository.loadDrillTypes()} returns fakeResponse

    }

    @Test
    fun `should update the livedata with correct data from the repository`(){

        drillsSelectionViewModel.fetchDrillTypes()

        assertEquals(arrayListOf(DrillsType("title1", "url1"), DrillsType("title2","url2")), getValue(drillsSelectionViewModel.drillsTypeList.value))

    }

}

These are the two classes that are extended in the test这是测试中扩展的两个类

class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {

    override fun beforeEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
            override fun executeOnDiskIO(runnable: Runnable) {
                runnable.run()
            }

            override fun postToMainThread(runnable: Runnable) {
                runnable.run()
            }

            override fun isMainThread(): Boolean {
                return true
            }
        })
    }

    override fun afterEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(null)
    }

}
class TestSchedulerExtension : BeforeTestExecutionCallback, AfterTestExecutionCallback {

    override fun beforeTestExecution(context: ExtensionContext?) {
        RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
        RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
        RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
        RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() }
    }

    override fun afterTestExecution(context: ExtensionContext?) {
        RxJavaPlugins.reset()
        RxAndroidPlugins.reset()
    }

}

This is the SUT这是 SUT

class DrillsSelectionViewModel(private val repository: DrillRepository): ViewModel() {

    private val compositeDisposable = CompositeDisposable()

    private var _drillsTypeList = MutableLiveData<ArrayList<DrillsType>>()
    val drillsTypeList: LiveData<ArrayList<DrillsType>>
        get() = _drillsTypeList


    init{

        fetchDrillTypes()
    }

    fun fetchDrillTypes()
    {

        val list = ArrayList<DrillsType>()

        viewModelScope.launch(Dispatchers.IO) {
            compositeDisposable += repository.loadDrillTypes()
                .subscribeWith(object : DisposableObserver<DrillsType>() {

                    override fun onError(e: Throwable) {
                        Log.d("TAG", e?.message)
                    }

                    override fun onNext(data: DrillsType) {
                        Log.d("TAG", "data is " + data.toString())
                        list.add(data)
                    }

                    override fun onComplete() {
                        Log.d("TAG", "COMPLETE")
                        _drillsTypeList.postValue(list)
                    }
                })
        }
    }


    override fun onCleared() {
        super.onCleared()
        if (!compositeDisposable.isDisposed) {
            compositeDisposable.dispose()
        }
    }

}

The DOC文件



    fun loadDrillTypes(): Observable<DrillsType> {
        return remoteDataSource.loadDrillTypes()
    }


    fun loadDrillList(drillType : String): Observable<Drill> {
        return remoteDataSource.loadDrillList(drillType)
    }
}

What I tried:我试过的:

  1. Removing the coroutines from the mix.从混合中删除协程。 I thought that removing "viewmodelscope.launch" would make the test work as I thought it had something to do with coroutines and them not finishing or something in time or not listening on the proper thread.我认为删除“viewmodelscope.launch”会使测试工作,因为我认为它与协程有关,并且它们没有完成或没有及时完成或没有在正确的线程上监听。

  2. Tried using the 2 extended classes posted below(to replace the Instant Execution Rule in JUnit4)尝试使用下面发布的 2 个扩展类(替换 JUnit4 中的即时执行规则)

  3. Googling a lot to find similar issues...谷歌搜索很多发现类似的问题......

Sorry for the long post, any more info or code that you need feel free to ask.If possible also point out some mistakes/ things that could be done better.抱歉,这篇文章很长,您需要的任何更多信息或代码都可以随时询问。如果可能,还可以指出一些错误/可以做得更好的事情。 Any answer with explanation is much appreciated.任何带有解释的答案都非常感谢。 I really want to understand what is the underlying issue.我真的很想了解根本问题是什么。 Thank you very much!非常感谢!

edit: I am considering it might be something due to RXKotlin编辑:我正在考虑这可能是由于 RXKotlin

In your setup() method, you're creating a viewmodel first and then configuring your mock reponse in the repository after.在您的setup()方法中,您首先创建一个视图模型,然后在存储库中配置您的模拟响应。 However, your viewmodel tries to get that response from the repository immediately on instantiation (in fetchDrillTypes() which is called from the init block), so it happens before the mock is ready.但是,您的视图模型会尝试在实例化时立即从存储库中获取该响应(在从init块调用的fetchDrillTypes()中),因此它会在模拟准备就绪之前发生。 You just need to reorder the calls in setup() so that drillsSelectionViewModel = ... happens last.您只需要对setup()的调用重新排序,以便drillsSelectionViewModel = ...最后发生。

I got this error on io.mockk:mockk:$mockkVersion:1.11.0 the day before it was working but on the next day when I closed Android Studio it throws the error.我在io.mockk:mockk:$mockkVersion:1.11.0工作前一天收到此错误,但在第二天关闭 Android Studio 时,它抛出错误。 I did Build > Clean Project and then Build > Rebuild Project and everything was ok.我做了 Build > Clean Project 然后 Build > Rebuild Project 一切正常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Android kotlin 单元测试 - 线程“Test worker @coroutine#4”中的异常 - Android kotlin unit test - Exception in thread "Test worker @coroutine#4" io.mockk.MockKException: no answer found for: View(#1) Android io.mockk.MockKException:找不到答案:日历(static Calendar#368的孩子).getTime() - Android io.mockk.MockKException: no answer found for: Calendar(child of static Calendar#368).getTime() io.mockk.MockKException:SignedCall 的匹配模拟签名失败 - io.mockk.MockKException: Failed matching mocking signature for SignedCall 获取错误 MockKException: no answer found for: Observer(#8).onChanged Android - Getting error MockKException: no answer found for: Observer(#8).onChanged Android declareMock&lt;&gt; 在 android 单元测试中不能使用 mockk - declareMock<> not working with mockk in android unit test Android 本地单元测试 - 使用 MockK 模拟 FirebaseAuth - Android Local Unit Test - Mock FirebaseAuth with MockK 单元测试因 MockResponse() 的 Error(MockKException) 而失败 - Unit test fail with Error(MockKException) for MockResponse() Android:如何使用 mockK 对 Observable concatMap 进行单元测试 - Android: How to unit test Observable concatMap compose with mockK 如何使用 MockK 在 Android 中测试 Kotlin 暂停调用? - How do I test a Kotlin suspend call in Android with MockK? 如何在 Mockk Kotlin 中测试异步 function - How to test async function in Mockk Kotlin
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM