mockito-kotlin icon indicating copy to clipboard operation
mockito-kotlin copied to clipboard

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

Open simon-tse-hs opened this issue 6 years ago • 11 comments

@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)

simon-tse-hs avatar Aug 08 '18 14:08 simon-tse-hs

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.

jachenry avatar Sep 06 '18 14:09 jachenry

@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 avatar Sep 17 '18 20:09 simon-tse-hs

@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
}

simon-tse-hs avatar Sep 17 '18 22:09 simon-tse-hs

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())

bohsen avatar Sep 18 '18 09:09 bohsen

@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()

jachenry avatar Sep 28 '18 03:09 jachenry

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

kschults avatar May 01 '19 16:05 kschults

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 avatar May 31 '19 21:05 jameswald

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.

jameswald avatar May 31 '19 22:05 jameswald

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()
}

davidxiasc avatar Jun 10 '19 19:06 davidxiasc

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.

grzegorzojdana avatar Jun 26 '19 09:06 grzegorzojdana

@jachenry @davidxiasc

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

jenzz avatar Jul 22 '19 21:07 jenzz