mockito / mockito-kotlin

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

Matchers are not working for suspend functions #269

Closed kostya-misura closed 5 years ago

kostya-misura commented 6 years ago

It seems that if you try to use "matchers" (any, eq, etc.) on suspend function mockito fails with "InvalidUseOfMatchersException".

following test

@Test
fun otherTest() {
    val foo: Foo = mock {
        on { runBlocking { bar(any()) } } doReturn ("message")
    }
    Mockito.validateMockitoUsage()
}

with following interface definition

interface Foo {
    suspend fun bar(arg: String): String
}

produces following error

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at org.app.Tests$otherTest$i$1$1$1.doResume(Tests.kt:76)
pablobaldez commented 6 years ago

Do you have some news about this issue? There is an alternative approach to do?

bohsen commented 6 years ago

What version of mockito-kotlin are you using? Using version 2.0.0-RC your test should pass.

kostya-misura commented 6 years ago

I will check and get back to you on this one. Thanks!

pablobaldez commented 6 years ago

I think it's still not working. I change the dependencies to use the version 2.0.0-RC but the results are the same:

@Before
fun before() {
    runBlocking {
        `when`(jsFacade.portfolioExtractTabScreen(anyString(), anyOrNull(), anyBoolean()))
            .thenReturn(Resource.success(MorpheusContent()))
   }
}

when I tried to run the tests i'm getting:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
4 matchers expected, 3 recorded
bohsen commented 6 years ago

You want this setup before each test, then try this:

class TestClass {
    private val facadeMock = mock<JsFacade> {
         onBlocking { portfolioExtractTabScreen(anyString(), anyOrNull(), anyBoolean()) } doReturn Resource.success(MorpheusContent())
    }

    ... All the tests ...
}

Overwrite it in tests using

...
@Test
fun myTest() = runBlocking {
    whenever(jsFacade.portfolioExtractTabScreen(anyString(), anyOrNull(), anyBoolean()))
            .thenReturn(Resource.failure(PortfolioException("Big mistake"))

   ... Rest of the testcode ...
}
...
pablobaldez commented 6 years ago

Thank you for your reply. I tried it and now i'm getting

java.lang.NoClassDefFoundError: kotlinx/coroutines/experimental/BuildersKt

I'm using Kotlin 1.3 and in its version coroutines are not experimental. Do you have support for this kotlin version?

nhaarman commented 6 years ago

2.0.0-RC3 has just been released that supports non-experimental coroutines. Try again?

pablobaldez commented 6 years ago

now it's working perfectly. My tests are passing. Thank you

andrewborba10 commented 6 years ago

Is there an example of how to mock a suspend function on an existing mock object? All the examples I've seen set the mock up as it's being instantiated. The constraint I'm working with is:

I have mock A built and given to me. Now, I need to mock a suspend API to return a value foo.

Example:

@Inject lateinit var mockA: A // using dagger to provide mocks configured in a separate module.

@Test
fun test1() = runBlocking {
    // how do i mock a suspend method on mockA?
}
ludddd commented 5 years ago

One more failing test for this issue: class MockTest { interface InterfaceA { suspend fun foo(i:Int) }

@Test
fun mock_test() = runBlocking{
    val mockObj = mock<InterfaceA>()
    mockObj.foo(10)
    verify(mockObj).foo(any())
}

}

fails with: org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Invalid use of argument matchers! 2 matchers expected, 1 recorded:

bohsen commented 5 years ago

@ludddd Are you using the correct imports? The example you provided passes ✅ when I try it.

@andrewborba10 You mock it like you would with mockito. Just use whenever and wrap the test with runBlocking. Modifying the example @ludddd provided:

class MockTest {
    interface InterfaceA {
        suspend fun foo(i:Int) : Int
    }

    @Test
    fun mock_test() {
        runBlocking{
            val mockObj = mock<InterfaceA>() // the mock in your case would be provided by dagger
            whenever(mockObj.foo(anyInt())).thenReturn(42)
            mockObj.foo(10)
            verify(mockObj).foo(any())
        }
    }
}
ludddd commented 5 years ago

I'm using: com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0 org.mockito:mockito-core:2.15.0 with kotlin 1.3 and kotlinx-coroutines 1.0.1 Got this problem when upgraded to kotlin1.3. Do you run the tests with the latest kotlin?

Imports in the file: import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify

bohsen commented 5 years ago

This is with the same dependencies as you, only using mockito-core 2.23.0. But when downgrading to 2.15.0 the test still passes.

ludddd commented 5 years ago

Test passed when I set mockito-core to 2.23.0. But fails with 2.15.0. Tried to switch mockito-core version to 2.15 in mockito-kotline repository - test fails too. And there is multiple test failures in CoroutineTest with it. Seems that mockito-core 2.15 is the cause.

andymoon commented 5 years ago

I had to set my dependencies as below in my gradle file for the matchers to work within a suspend.

dependencies {
            testCompile 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0'
            testCompile("org.mockito:mockito-core:2.23.0")
}
nhaarman commented 5 years ago

Seems to have been resolved :+1:

lborisevich-clgx commented 3 years ago

@nhaarman looks like it's back

This produces the same error:

mock<suspend (Int) -> Int> {
    onBlocking { invoke(any()) } doReturn 1
}

I am using: mockito-kotlin 2.2.0 mockito-core 3.6.28 kotlin 1.4.21

xDPa commented 3 years ago

same problem as @lborisevich-clgx . high order functions doesn't seem to work:

val myFunction = mock<suspend (Int) -> Boolean>()

myFunction.stub {
    onBlocking { invoke(any()) }.doReturn(false)
}
org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
2 matchers expected, 1 recorded:

but works with zero parameters:

val myFunction = mock<suspend () -> Boolean>()

myFunction.stub {
    onBlocking { invoke() }.doReturn(false)
}
JcMinarro commented 2 years ago

Same that @xDPa And verify doesn't work neither.

@Test
    fun test() = runTest {
        val myFunction: suspend (Int) -> Boolean = mock()
        myFunction(1)

        verify(myFunction).invoke(eq(1))
    }

The log is:

Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at io.getstream.Tests$test$1.invokeSuspend(Tests.kt:15)

This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
    //correct:
    someMethod(any(), eq("String by matcher"));

For more info see javadoc for Matchers class.

I'm using: org.mockito:mockito-core:4.3.1 org.mockito.kotlin:mockito-kotlin:4.0.0