mockito / mockito-kotlin

Using Mockito with Kotlin
MIT License
3.11k stars 202 forks source link

mockito-kotlin cannot verify higher-order functions in Robolectric Tests #272

Open simon-tse-hs opened 6 years ago

simon-tse-hs commented 6 years ago

@nhaarman , the area that I was trying to test was like this

val testArg = mock<(String) -> Unit>()
val result = someClass.doSomething(testArg)
verify(testArg).invoke(any())

And the error comes out as the folllwing

org.mockito.exceptions.misusing.UnfinishedVerificationException: 
Missing method call for verify(mock) here:
-> at com.nhaarman.mockitokotlin2.VerificationKt.verify(Verification.kt:42)

Example of correct verification:
    verify(mock).doSomething()

Also, this error might show up because you verify either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.

    at org.mockito.internal.junit.JUnitRule$1.evaluate(JUnitRule.java:44)
    at com.hootsuite.testing.rule.RxJavaSchedulerRule$apply$1.evaluate(RxJavaSchedulerRule.kt:31)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:253)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:130)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:42)
...
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)
    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.application.AppMainV2.main(AppMainV2.java:131)
jachenry commented 6 years ago

We're seeing the same issue in our project. Our workaround is to create a test only interface that satisfies the higher-order function and can be verified. Using your example it'd look like:

val testCallback = mock<Callback>()
val result = someClass.doSomething(testCallback::invoke)
verify(testCallback).invoke(any())

interface Callback {
  operator fun invoke(value: String)
}

Less than ideal but the only workaround we've found. Please let me know if there is an easier way.

simon-tse-hs commented 6 years ago

@jachenry I will use your idea for now. I wish I had some hint from @nhaarman maybe what we can do to fix it as well :)

simon-tse-hs commented 6 years ago

@jachenry I made these temporary SMI's to help. But these are also normally generated by Kotlin

interface Function0 {
    operator fun invoke()
}

interface Function1<in P1, out R> {
    operator fun invoke(p1: P1): R
}

interface Function2<in P1, in P2, out R> : Function<R> {
    operator fun invoke(p1: P1, p2: P2): R
}

interface Function3<in P1, in P2, in P3, out R> : Function<R> {
    operator fun invoke(p1: P1, p2: P2, p3: P3): R
}
bohsen commented 6 years ago

val testArg = mock<(String) -> Unit>() val result = someClass.doSomething(testArg) verify(testArg).invoke(any())

Did you try changing any() to anyString()? This is a very common issue with Mockito-kotlin.

val testArg = mock<(String) -> Unit>()
val result = someClass.doSomething(testArg)
verify(testArg).invoke(anyString())
jachenry commented 6 years ago

@bohsen I have not tried that. But I don't believe that's the root cause considering it also exists when verifying no-arg functions.

val somethingCallback = mock<() -> Unit>()
val result = someClass.doSomething(somethingCallback)
verify(somethingCallback).invoke()
kschults commented 5 years ago

For what it's worth, the behavior that I'm experiencing is that the first call to verify in a test run succeeds, but the next call to verify or mock (in the same test or in the next test) will throw this exception. Using 1.6.0

jameswald commented 5 years ago

I don't think this issue is specific to com.nhaarman.mockitokotlin2:mockito-kotlin because the following test case reproduces the failure with only org.mockito:mockito-inline and org.robolectric:robolectric:

package com.example.test

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify

@RunWith(AndroidJUnit4::class)
class Issue272Test {

    @Suppress("UNCHECKED_CAST")
    private val function = mock(Function1::class.java) as (String) -> Unit

    @Test
    fun test1() {
        function.invoke("test")
        verify(function).invoke("test")
    }

    @Test
    fun test2() {
        function.invoke("test")
        verify(function).invoke("test")
    }
}

Failure:

org.mockito.exceptions.misusing.UnfinishedVerificationException: 
Missing method call for verify(mock) here:
-> at com.example.test.Issue272Test.test1(Issue272Test.kt:18)

Example of correct verification:
    verify(mock).doSomething()

Also, this error might show up because you verify either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.

    at com.example.test.Issue272Test.<init>(Issue272Test.kt:13)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:217)
    at org.robolectric.RobolectricTestRunner$HelperTestRunner.createTest(RobolectricTestRunner.java:534)
    at org.junit.runners.BlockJUnit4ClassRunner$1.runReflectiveCall(BlockJUnit4ClassRunner.java:266)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:263)
    at org.robolectric.internal.SandboxTestRunner$HelperTestRunner.methodBlock(SandboxTestRunner.java:322)
    at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:252)
    at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
jameswald commented 5 years ago

I reported this issue to Robolectric with https://github.com/robolectric/robolectric/issues/5076. I haven't been able to rule out Mockito but that is working fine on both JVM (without Robolectric) and Android devices.

davidxiasc commented 5 years ago

Here's a neat little workaround that you can do:


private class Callback : () -> Unit {
    override fun invoke() = Unit
}

private val callback = mock<Callback>()

@Test
fun testFoo() {
    ...
    verify(callback).invoke()
}
grzegorzojdana commented 5 years ago

Another workaround is to use spy:

val testArg = spy<(String) -> Unit> { _ -> }
val result = someClass.doSomething(testArg)
verify(testArg).invoke(any())

[EDIT] Seems that this trick doesn't work with mockito-kotlin 2.x (does work with 1.6.0). @jenzz solution suggested below works.

jenzz commented 5 years ago

@jachenry @davidxiasc

Simply declaring a private interface Callback : () -> Unit is sufficient as a workaround.