mockito / mockito-kotlin

Using Mockito with Kotlin
MIT License
3.09k stars 198 forks source link

NullPointerException happens when mock a suspend function B called by another function A #432

Closed ToffeeLu closed 3 years ago

ToffeeLu commented 3 years ago

Below code should print 3, but actually it throws below error. Even after I remove all suspend, the print value is 0 instead of 3. It can be reproduced locally even I copy this test to this repo, https://github.com/mockito/mockito-kotlin/blob/main/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt

import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

class A{
    suspend fun a():Int{
        return b()
    }
    suspend fun b(): Int{
        return 2
    }
}

class HelloTest {
    @Test
    fun testBWasCalledByA() = runBlocking{
        val a = mock<A>{
            onBlocking { b() } doReturn 3
        }
        val res = a.a()
        println(res)
    }
}
java.lang.NullPointerException: Cannot invoke "java.lang.Number.intValue()"

                at org.example.HelloTest$testBWasCalledByA$1.invokeSuspend(HelloTest.kt:24)

                at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

                at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)

                at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)

                at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)

                at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)

                at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)

                at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)

                at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)

                at org.example.HelloTest.testBWasCalledByA(HelloTest.kt:20)

                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)

                at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

                at java.base/java.lang.reflect.Method.invoke(Method.java:564)

                at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:389)

                at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)

                at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:167)

                at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)

                at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:163)

                at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:110)

                at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:57)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$execute$3(HierarchicalTestExecutor.java:83)

                at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:77)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$null$2(HierarchicalTestExecutor.java:92)

                at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)

                at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)

                at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)

                at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)

                at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)

                at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)

                at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)

                at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)

                at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)

                at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$execute$3(HierarchicalTestExecutor.java:92)

                at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:77)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$null$2(HierarchicalTestExecutor.java:92)

                at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)

                at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)

                at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)

                at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)

                at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)

                at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)

                at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)

                at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)

                at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)

                at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.lambda$execute$3(HierarchicalTestExecutor.java:92)

                at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:77)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:51)

                at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)

                at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)

                at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)

                at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)

                at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)

                at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)

                at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)

                at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
bohsen commented 3 years ago
@Test
fun testBWasCalledByA() = runBlocking{
    val a = mock<A>{
        onBlocking { b() } doReturn 3
    }
    val res = a.a()
    println(res)
}

@ToffeeLu As for the example you've provided you invoke an unstubbed function on your mock (this line: val res = a.a()) . Maybe try:

open class A{
    open suspend fun a():Int{
        return b()
    }
    open suspend fun b(): Int{
        return 2
    }
}

class HelloTest {
    @Test
    fun testBWasCalledByA() = runBlocking{
        val a = mock<A>{
            onBlocking { b() } doReturn 3
        }
        val res = a.b() // invoke the stubbed function instead
        println(res)
    }
}

I've also added open declarations to your class under test (class A). Not needed if you use inline-mockmaker, AllOpen-plugin or similar.

ToffeeLu commented 3 years ago

@bohsen Thanks for the reply along with the open thing. But how can I verify b() is called by a()? That's what I want to verify by UT. If I also mock a(), then it's meaningless.

ToffeeLu commented 3 years ago

@bohsen I just figured out myself. Add this line helps doCallRealMethod().whenever(a).a().

I'm wondering is it possible to enrich the wiki? It's quite simple now. Otherwise people need to read Mockito document entirely, and then find APIs here.

    @Test
    fun testBWasCalledByA() = runBlocking{
        val a = mock<A>{
            onBlocking { b() } doReturn 3
        }
        doCallRealMethod().whenever(a).a()
        val res = a.a()
        println(res)
    }
bohsen commented 3 years ago

@ToffeeLu Sorry for not reading the code/issue thoroughly. Good to hear you found the solution anyway.

Regarding the wiki, PR's are welcome I believe.

ToffeeLu commented 3 years ago

Thanks @bohsen , close for now. Will send PR later for the WIKI.