mockito / mockito-kotlin

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

doNothing() cannot be used on suspend Unit functions #350

Closed Nimrodda closed 5 years ago

Nimrodda commented 5 years ago

Mocking Unit method where the first call should doNothing() and then 2nd doThrow(), produces the following error:

Only void methods can doNothing()!

Example:

doNothing().doThrow(RuntimeException::class.java).whenever(mock).foo() where foo() is suspend fun foo().

nhaarman commented 5 years ago

Mockito.doNothing() and its related Stubber functions are provided as a workaround for "rare occasions when you cannot use Mockito.when(Object)". One example is stubbing methods that return Void: the Java compiler doesn't allow Void inside brackets.

In this case, you're not returning Void, you're returning Unit, and you're better off returning to the non-workaround solutions: whenever(mock.foo()).thenThrow(RuntimeException::class.java) is allowed.

Full example:

class StubSuspendUnitTest {

    @Test
    fun `stubbing on suspending unit`() = runBlocking {
        val testSubject: SomeInterface = mock()

        whenever(testSubject.suspendingUnit())
            .thenReturn(Unit)
            .thenThrow(RuntimeException("Foo"))

        expect(testSubject.suspendingUnit()).toBe(Unit)
        expectErrorWithMessage("Foo") on { runBlocking { testSubject.suspendingUnit() } }
    }
}

interface SomeInterface {

    suspend fun suspendingUnit(): Unit
}
Nimrodda commented 5 years ago

Oh yeah, I can't believe I missed that. Unit after all is just an object... Thanks a lot! :)

radityagumay commented 4 years ago

i did something like this

when(foo.save("key", "value")).thenReturn(Unit)
iadcialim commented 4 years ago

@nhaarman In my case, when a suspend function of a mocked or spied object is called, I don't want it to do whatever it has inside. So its like doNothing but that now for functions that return a Unit. But now it does not work as specified above. Any workaround for this?

radityagumay commented 4 years ago

@iadcialim could you please share you gist?

gkylafas commented 4 years ago

@radityagumay I believe this is the problem that @iadcialim is facing:

class StubSuspendingTest {
    @Test
    fun `stubbing on suspending unit`() = runBlocking {
        val testSubject: SomeClass = spy(SomeClass()) // NOTE: spy vs mock

        whenever(testSubject.suspendingUnit()) // this calls the actual method impl
            .thenReturn(Unit)
            .thenThrow(RuntimeException("Foo"))

        assertEquals(Unit, testSubject.suspendingUnit())
        try {
            testSubject.suspendingUnit()
        } catch (error: Throwable) {
            assertEquals("Foo", error.message)
        }
    }
}

interface SomeInterface {
    suspend fun suspendingUnit(): Unit
}

class SomeClass : SomeInterface {
    override suspend fun suspendingUnit() {
        TODO("not implemented")
    }
}
gkylafas commented 3 years ago

@iadcialim You can use

doReturn(Unit).whenever(spy).functionReturningUnit()

which does not execute functionReturningUnit() as

whenever(spy.functionReturningUnit()).thenReturn(Unit)

would.