如何在Mockk中模拟返回密封类的函数?

huangapple go评论58阅读模式
英文:

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&#39;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&#39;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>

huangapple
  • 本文由 发表于 2023年2月8日 16:28:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/75383044.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定