mockito: [Kotlin] Mocking fails when one suspend function calls other suspend function

When trying to mock class that has suspend function that calls other suspend function, it fails with exception:

Exception in thread "main" org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class com.myapp.SuspendableClass.
Can not mock final classes with the following settings :
- explicit serialization (e.g. withSettings().serializable())
- extra interfaces (e.g. withSettings().extraInterfaces(...))

You are seeing this disclaimer because Mockito is configured to create inlined mocks.
You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.

Underlying exception : org.mockito.exceptions.base.MockitoException: Could not modify all classes [class java.lang.Object, class com.myapp.SuspendableClass]
at com.myapp.DupaKt$main$1.doResume(dupa.kt:25)
at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resume(CoroutineImpl.kt:54)
at kotlinx.coroutines.experimental.DispatchTask.run(CoroutineDispatcher.kt:120)
at kotlinx.coroutines.experimental.EventLoopBase$QueuedRunnableTask.run(EventLoop.kt:189)
at kotlinx.coroutines.experimental.EventLoopBase.processNextEvent(EventLoop.kt:129)
at kotlinx.coroutines.experimental.BlockingCoroutine.joinBlocking(Builders.kt:225)
at kotlinx.coroutines.experimental.BuildersKt.runBlocking(Builders.kt:150)
at kotlinx.coroutines.experimental.BuildersKt.runBlocking$default(Builders.kt:142)
at com.myapp.DupaKt.main(dupa.kt:24)
Caused by: org.mockito.exceptions.base.MockitoException: Could not modify all classes [class java.lang.Object, class com.myapp.SuspendableClass]
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:138)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:346)
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:161)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:355)
... 9 more
Caused by: java.lang.IllegalStateException:
Byte Buddy could not instrument all classes within the mock's type hierarchy

This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:
- Compiled by older versions of scalac
- Classes that are part of the Android distribution
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:120)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.mockClass(InlineBytecodeGenerator.java:97)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$1.call(TypeCachingBytecodeGenerator.java:37)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator$1.call(TypeCachingBytecodeGenerator.java:34)
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:138)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:346)
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:161)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:355)
at org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.mockClass(TypeCachingBytecodeGenerator.java:32)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createMockType(InlineByteBuddyMockMaker.java:201)
at org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker.createMock(InlineByteBuddyMockMaker.java:182)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:35)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:63)
at org.mockito.Mockito.mock(Mockito.java:1729)
at org.mockito.Mockito.mock(Mockito.java:1642)
... 9 more
Caused by: java.lang.IllegalStateException: public final java.lang.Object com.myapp.SuspendableClass.fetch(boolean,kotlin.coroutines.experimental.Continuation) is inconsistent at 1: java/lang/Object
at net.bytebuddy.asm.Advice$StackMapFrameHandler$Default.translateFrame(Advice.java:1200)
at net.bytebuddy.asm.Advice$StackMapFrameHandler$Default.translateFrame(Advice.java:1141)
at net.bytebuddy.asm.Advice$AdviceVisitor.visitFrame(Advice.java:6636)
at net.bytebuddy.jar.asm.ClassReader.a(Unknown Source)
at net.bytebuddy.jar.asm.ClassReader.b(Unknown Source)
at net.bytebuddy.jar.asm.ClassReader.accept(Unknown Source)
at net.bytebuddy.jar.asm.ClassReader.accept(Unknown Source)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:2910)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1628)
at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:171)
at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:92)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:2560)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.transform(InlineBytecodeGenerator.java:167)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)
at org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.triggerRetransformation(InlineBytecodeGenerator.java:117)
... 23 more

Process finished with exit code 1

This is the simples example I could come up with to present this bug:

class SuspendableClass {
    suspend fun fetch(updateOnly: Boolean = true): Int {
//uncomment to fix mocking
//        runBlocking {
        fetchConcrete()
//        }
        return 2
    }

    suspend fun fetchConcrete() = 1
}

fun main(args: Array<String>) = runBlocking<Unit> {
    val mockClass = Mockito.mock(SuspendableClass::class.java)

    Mockito.`when`(mockClass.fetch()).thenReturn(10)

    MatcherAssert.assertThat(mockClass.fetch(), IsEqual(10))
    Mockito.verify(mockClass).fetch()
}

Important notes:

  • mock-maker-inline is used to mock final classes
  • mockito version: 2.8.47
  • making class and all it’s methods open allows mocking
  • running inner suspend function inside runBlocking allows mocking

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 4
  • Comments: 28 (21 by maintainers)

Commits related to this issue

Most upvoted comments

The bytecode generation strategy was adjusted in Kotlin 1.1.4-3 which fixes this issue. See KT-19713 for details. I’ve updated PR #1165 with the test for this issue to Kotlin 1.1.4-3 and it passes. I suggest to close this issue and to merge PR #1165 into Mockito codebase to increase test coverage.

I’m getting this again with 2.17.0. It works with 2.16.0

@sinwe maybe is not your case, but I found this issue by searching in google… so this comment could be helpful for somebody else:

In my case, errors started after installing JAVA 10 in my laptop. Switching back to JAVA 8 made mockito working as expected again.

(I’m using kotlin).

I have a solution drafted out that does not increase the amout of byte codes. Hope to complete it by the end of September.