question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

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

Issue Analytics

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

github_iconTop GitHub Comments

3reactions
elizarovcommented, Aug 30, 2017

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.

2reactions
sinwecommented, Mar 26, 2018

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

Read more comments on GitHub >

github_iconTop Results From Across the Web

Mocking and stubbing suspend functions with MockK
How to easily stub or mock suspend function in Kotlin? MockK is the solution. See MockK examples for coroutines testing.
Read more >
In kotlin, how do I mock a suspend function that wraps a ...
The above syntax fails with a mockito exception because it's expecting a matcher for the callback. org.mockito.exceptions.misusing.
Read more >
Verifying suspending functions with Mockito (or alternatives)
I've run into trouble using Mockito to verify invocations of suspending functions. This appears to be related to how suspending functions ...
Read more >
Coroutines and suspend functions | Mocking | MockK Guidebook
... function literals to create stubs, small changes are needed to stub suspend functions. MockK provides functions prefixed with co as equivalents to...
Read more >
Testing Kotlin Coroutines - Kt. Academy
Testing suspending functions in most cases is not different from testing normal functions. Take a look at the fetchUserData below from FetchUserUseCase ....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found