m0s*_*it0 4 junit kotlin mockk
我想从标准库类中模拟resume和。两者都是扩展函数。resumeWithExceptionContinuation
这是我的 JUnit 设置函数:
@MockK
private lateinit var mockContinuation: Continuation<Unit>
@Before
fun setup() {
MockKAnnotations.init(this)
mockkStatic("kotlin.coroutines.ContinuationKt")
every { mockContinuation.resume(any()) } just Runs
every { mockContinuation.resumeWithException(any()) } just Runs
}
Run Code Online (Sandbox Code Playgroud)
但是这不起作用,在模拟resumeWithException函数时抛出以下异常:
io.mockk.MockKException: Failed matching mocking signature for
SignedCall(retValue=java.lang.Void@5b057c8c, isRetValueMock=false, retType=class java.lang.Void, self=Continuation(mockContinuation#1), method=resumeWith(Any), args=[null], invocationStr=Continuation(mockContinuation#1).resumeWith(null))
left matchers: [any()]
at io.mockk.impl.recording.SignatureMatcherDetector.detect(SignatureMatcherDetector.kt:99)
at io.mockk.impl.recording.states.RecordingState.signMatchers(RecordingState.kt:39)
at io.mockk.impl.recording.states.RecordingState.round(RecordingState.kt:31)
at io.mockk.impl.recording.CommonCallRecorder.round(CommonCallRecorder.kt:50)
at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:59)
at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30)
at io.mockk.MockKDsl.internalEvery(API.kt:92)
at io.mockk.MockKKt.every(MockK.kt:104)
at com.blablabla.data.pair.TestConnectSDKDeviceListener.setup(TestConnectSDKDeviceListener.kt:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Run Code Online (Sandbox Code Playgroud)
这是与以下resumeWithException内容非常相似的代码resume:
/**
* Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
* last suspension point.
*/
@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
resumeWith(Result.failure(exception))
Run Code Online (Sandbox Code Playgroud)
这是一个小调查。如果您只是在寻找内联函数和内联类的解决方案,请滚动到解决方案部分。
长解释:
这些是现代 kotlin 功能的棘手后果。让我们在 kotlin 插件的帮助下将此代码反编译为 java。这mockContinuation.resumeWithException(any())变得像这样(缩短和美化版本)
Matcher matcher = (new ConstantMatcher(true));
Throwable anyThrowable = (Throwable)getCallRecorder().matcher(matcher, Reflection.getOrCreateKotlinClass(Throwable.class));
Object result = kotlin.Result.constructor-impl(ResultKt.createFailure(anyThrowable));
mockContinuation.resumeWith(result);
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,发生了一些事情。首先,没有再调用resumeWithException了。因为是内联函数,所以被编译器内联了,所以现在是resumeWith调用了。其次,由返回的匹配器any()被一个神秘的 call 包裹
kotlin.Result.constructor-impl(ResultKt.createFailure(anyThrowable)),它不是一个函数的参数,在模拟上调用。这就是为什么 mockk 无法匹配签名和匹配器的原因。显然我们可以尝试通过模拟resumeWith函数本身来修复它:
every { mockContinuation.resumeWith(any()) } just Runs
Run Code Online (Sandbox Code Playgroud)
它也不起作用!这是反编译的代码:
Matcher matcher = (new ConstantMatcher(true));
Object anyValue = getCallRecorder().matcher(matcher, Reflection.getOrCreateKotlinClass(kotlin.Result.class));
mockContinuation.resumeWith(((kotlin.Result)anyValue).unbox-impl());
Run Code Online (Sandbox Code Playgroud)
这是另一个神秘的电话unbox-impl()。让我们看一下Result类定义
public inline class Result<out T> @PublishedApi internal constructor(
@PublishedApi
internal val value: Any?
)
Run Code Online (Sandbox Code Playgroud)
这是一个内联类!并且ubox-impl()是一个编译器生成的函数,如下所示:
public final Object unbox-impl() {
return this.value;
}
Run Code Online (Sandbox Code Playgroud)
基本上,编译器内联Result对象,将其替换为value. 因此,再次调用,而不是resumeWith(any())最后调用resumeWith(any().value),模拟库被混淆了。那么如何嘲讽呢?请记住,mockContinuation.resume(any())出于某种原因工作,即使resume只是另一个内联函数
public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
Run Code Online (Sandbox Code Playgroud)
反编译mockContinuation.resume(any())给了我们
Object anyValue = getCallRecorder().matcher(matcher, Reflection.getOrCreateKotlinClass(Unit.class));
Object result = kotlin.Result.constructor-impl(anyValue);
mockContinuation.resumeWith(result);
Run Code Online (Sandbox Code Playgroud)
正如我们所看到的,它确实是内联的,并且resumeWith是用一个result对象调用的,而不是用anyValue,它是我们的匹配器。但是,让我们来看看这个神秘的kotlin.Result.constructor-impl:
public static Object constructor-impl(Object value) {
return value;
}
Run Code Online (Sandbox Code Playgroud)
所以它实际上没有包装价值,只是返回它!这就是为什么它实际上有效并为我们提供了如何模拟的解决方案resumeWith:
every { mockContinuation.resumeWith(Result.success(any())) } just Runs
Run Code Online (Sandbox Code Playgroud)
是的,我们正在将我们的匹配器包装到 中Result,正如我们所见,它被内联了。但是如果我们想区分Result.success()和Result.failure()呢?我们仍然不能 mock mockContinuation.resumeWith(Result.failure(any())),因为failure()call 将参数包装成其他东西(检查上面的源代码或反编译代码)。所以我可以考虑这样的事情:
every { mockContinuation.resumeWith(Result.success(any())) } answers {
val result = arg<Any>(0)
if (result is Unit) {
println("success")
} else {
println("fail")
}
}
Run Code Online (Sandbox Code Playgroud)
该result值是我们的类型(Unit在这种情况下)或Result.Failure类型的实例,这是一个内部类型。
解决方案:
mock.testFunction(any<InlinedClass>())使用mock.testFunction(InlinedClass(any<Value>())). 这是mockk支持内联类的功能请求,目前处于 Opened 状态。
| 归档时间: |
|
| 查看次数: |
3148 次 |
| 最近记录: |