Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. I am using Mockk and 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.

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

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.

  2. Tried using the 2 extended classes posted below(to replace the Instant Execution Rule in 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

like image 790
Sir Isaac Avatar asked Feb 18 '20 19:02

Sir Isaac


1 Answers

In your setup() method, you're creating a viewmodel first and then configuring your mock reponse in the repository after. 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. You just need to reorder the calls in setup() so that drillsSelectionViewModel = ... happens last.

like image 187
RussHWolf Avatar answered Oct 09 '22 15:10

RussHWolf