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 insiderunBlocking
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
- Removed check that local variables in frame have the same types as parameters. This check is too strict and was rejecting valid class files. It fixes mockito issue 1152: https://github.com/mockito/moc... — committed to elizarov/byte-buddy by elizarov 7 years ago
- Introduce test for mock-maker-inline with Kotlin suspend functions See issue #1152 This test should pass when byte-buddy is updated with https://github.com/raphw/byte-buddy/pull/332 — committed to elizarov/mockito by elizarov 7 years ago
- Introduce test for mock-maker-inline with Kotlin suspend functions See issue #1152 — committed to elizarov/mockito by elizarov 7 years ago
- Introduce test for mock-maker-inline with Kotlin suspend functions See issue #1152 — committed to elizarov/mockito by elizarov 7 years ago
- Introduce test for mock-maker-inline with Kotlin suspend functions (#1165) See issue #1152 — committed to mockito/mockito by elizarov 7 years ago
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.