英文:
How to mock functions returning a sealed class in Mockk?
问题
当使用Mockk来模拟返回封闭类(sealed class)的函数时,会导致Mockk崩溃,并生成堆栈跟踪。这可能是由于Mockk的某种限制或问题引起的,您可能需要等待Mockk的更新或修复。关于如何正确地使用Mockk来模拟返回封闭类的函数,您可以关注相关的GitHub问题页面,以获取最新的信息和解决方案。
GitHub问题页面链接:https://github.com/mockk/mockk/issues/1041
请注意,由于这个问题是在2022年之前的知识截止日期内发生的,我无法提供关于该问题的最新信息。
英文:
When stubbing a function returning a sealed class with Mockk as following
File BoeTest.kt
package nl.dstibbe.example
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
sealed class MySeal
data class MyOtherSeal(val value:String) : MySeal()
class MyClass{
fun someMethod(b: String): MySeal {
return MyOtherSeal("hooray")
}
}
class SealTest {
val mockedClass:MyClass = mockk()
@Test
fun `test me`() {
every { mockedClass.someMethod(any()) } returns MyOtherSeal("something else") //Fails
}
}
will cause Mockk to crash with the stacktrace
java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the class NestHost, NestMembers, Record, or PermittedSubclasses attribute
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:169)
at io.mockk.proxy.jvm.transformation.JvmInlineInstrumentation.retransform(JvmInlineInstrumentation.kt:28)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation$doCancel$1.invoke(RetransformInlineInstrumentation.kt:38)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation$doCancel$1.invoke(RetransformInlineInstrumentation.kt:32)
at io.mockk.proxy.common.transformation.ClassTransformationSpecMap.applyTransformation(ClassTransformationSpecMap.kt:41)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation.doCancel(RetransformInlineInstrumentation.kt:32)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation.access$doCancel(RetransformInlineInstrumentation.kt:6)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation$execute$1$1.invoke(RetransformInlineInstrumentation.kt:17)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation$execute$1$1.invoke(RetransformInlineInstrumentation.kt:17)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation.execute(RetransformInlineInstrumentation.kt:23)
at io.mockk.proxy.jvm.ProxyMaker.inline(ProxyMaker.kt:88)
at io.mockk.proxy.jvm.ProxyMaker.proxy(ProxyMaker.kt:30)
at io.mockk.impl.instantiation.JvmMockFactory.newProxy(JvmMockFactory.kt:34)
at io.mockk.impl.instantiation.AbstractMockFactory.newProxy$default(AbstractMockFactory.kt:24)
at io.mockk.impl.instantiation.AbstractMockFactory.temporaryMock(AbstractMockFactory.kt:127)
at io.mockk.impl.recording.states.RecordingState$call$temporaryMock$1.invoke(RecordingState.kt:69)
at io.mockk.impl.instantiation.JvmAnyValueGenerator$anyValue$2.invoke(JvmAnyValueGenerator.kt:35)
at io.mockk.impl.instantiation.AnyValueGenerator.anyValue(AnyValueGenerator.kt:34)
at io.mockk.impl.instantiation.JvmAnyValueGenerator.anyValue(JvmAnyValueGenerator.kt:31)
at io.mockk.impl.recording.states.RecordingState.call(RecordingState.kt:75)
at io.mockk.impl.recording.CommonCallRecorder.call(CommonCallRecorder.kt:53)
at io.mockk.impl.stub.MockKStub.handleInvocation(MockKStub.kt:271)
at io.mockk.impl.instantiation.JvmMockFactoryHelper$mockHandler$1.invocation(JvmMockFactoryHelper.kt:24)
at io.mockk.proxy.jvm.advice.Interceptor.call(Interceptor.kt:21)
at nl.dstibbe.example.MyClass.someMethod(BoeTest.kt:20)
at nl.dstibbe.example.BoeTest$test me$1.invoke(BoeTest.kt:12)
at nl.dstibbe.example.BoeTest$test me$1.invoke(BoeTest.kt:12)
at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$1.invoke(RecordedBlockEvaluator.kt:25)
at io.mockk.impl.eval.RecordedBlockEvaluator$enhanceWithRethrow$1.invoke(RecordedBlockEvaluator.kt:78)
at io.mockk.impl.recording.JvmAutoHinter.autoHint(JvmAutoHinter.kt:23)
at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:40)
at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
at io.mockk.MockKDsl.internalEvery(API.kt:94)
at io.mockk.MockKKt.every(MockK.kt:143)
at nl.dstibbe.example.BoeTest.test me(BoeTest.kt:12)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
I must be doing something wrong since Mockk was created for Kotlin and sealed classes are hardly a new thing.
How does one mock a function returning a sealed class in Mockk properly?
Corresponding github issue: https://github.com/mockk/mockk/issues/1041
答案1
得分: 2
(I get a different stack trace but am using Kotlin 1.8.10 and Mockk 1.12.4 but still able to reproduce the issue.)
我得到了一个不同的堆栈跟踪,但我使用的是 Kotlin 1.8.10 和 Mockk 1.12.4,但仍然能够重现这个问题。
I think this case is on the edge of sealed classes/interfaces and a recent tightening of the rules. We were unwittingly relying on a defect in Kotlin 1.7 where mocks could be created on sealed interfaces (or more specifically like you, subclasses of the sealed item). This loophole is closed in 1.8.10.
我认为这个情况涉及到密封类/接口以及最近对规则的收紧。我们不知不觉地依赖于 Kotlin 1.7 中的一个缺陷,即可以在密封接口上创建模拟对象(或更具体地说,像您一样,密封项的子类)。这个漏洞在1.8.10中已经关闭。
You probably already know that if you change MyClass.someMethod
to return MyOtherSeal
then the problem goes away. So it's all down to the fact that mockk attempts to create a mock instance of MySeal
which it cannot do. This does seem strange - why does it need to do this when your every/returns
does not pass another mockk()
but a genuine concrete valid object? Perhaps this is a shortcoming Mockk could address.
你可能已经知道,如果你将 MyClass.someMethod
改为返回 MyOtherSeal
,问题就会消失。所以问题的关键在于,mockk 试图创建一个 MySeal
的模拟实例,但它无法做到。这似乎很奇怪 - 为什么在你的 every/returns
中不传递另一个 mockk()
而是一个真正的具体有效对象时,它需要这样做呢?也许这是 Mockk 可能需要解决的一个不足之处。
So you are not doing anything wrong, you've hit upon the Kotlin 1.8 restriction that Mockk might be able to workaround...
所以你没有做错任何事情,你遇到了 Kotlin 1.8 的限制,Mockk 可能能够解决这个问题...
Anyway, I have a workaround. Ugly, but it might suit your purposes.
无论如何,我有一个解决方法。虽然不太美观,但可能适合你的需求。
Using [this exception for indirect subclasses of sealed classes][1] you could create an intermediary class that is open, which allows mockk to do its thing:
使用[这种方法来处理密封类的间接子类][1],你可以创建一个中介类,这个中介类是开放的,允许 mockk 完成它的工作:
open class Unsealed : MySeal()
data class MyOtherSeal(val value:String) : Unsealed()
class MyClass{
fun someMethod(b: String): Unsealed {
return MyOtherSeal("hooray")
}
}```
[1]: https://kotlinlang.org/docs/sealed-classes.html#location-of-direct-subclasses
<details>
<summary>英文:</summary>
(I get a different stack trace but am using Kotlin 1.8.10 and Mockk 1.12.4 but still able to reproduce the issue.)
I think this case is on the edge of sealed classes/interfaces and a recent tightening of the rules. We were unwittingly relying on a defect in Kotlin 1.7 where mocks could be created on sealed interfaces (or more specifically like you, subclasses of the sealed item). This loophole is closed in 1.8.10.
You probably already know that if you change `MyClass.someMethod` to return `MyOtherSeal` then the problem goes away. So it's all down to the fact that mockk attempts to create a mock instance of `MySeal` which it cannot do. This does seem strange - why does it need to do this when your `every/returns` does not pass another `mockk()` but a genuine concrete valid object? Perhaps this is a shortcoming Mockk could address.
So you are not doing anything wrong, you've hit upon the Kotlin 1.8 restriction that Mockk might be able to workaround...
Anyway, I have a workaround. Ugly, but it might suit your purposes.
Using [this exception for indirect subclasses of sealed classes][1] you could create an intermediary class that is open, which allows mockk to do its thing:
sealed class MySeal
open class Unsealed : MySeal()
data class MyOtherSeal(val value:String) : Unsealed()
class MyClass{
fun someMethod(b: String): Unsealed {
return MyOtherSeal("hooray")
}
}
[1]: https://kotlinlang.org/docs/sealed-classes.html#location-of-direct-subclasses
</details>
# 答案2
**得分**: 0
我找到了原因和解决办法:mockk 依赖于 byte-buddy 1.12.20,然而某些其他依赖项(例如 mockito-kotlin)提供了 byte-buddy 的不同版本(在我的情况下是 1.10.22)。排除其他版本并强制使用 1.12.20 解决了我的问题。
<details>
<summary>英文:</summary>
I have found the cause and solution: mockk depends on byte-buddy 1.12.20 , certain dependencies however (e.g. mockito-kotlin ) provide a different version of byte-buddy (in my case 1.10.22) . Excluding the other versions and forcing 1.12.20 fixed my issues.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论