MockK class 使用 Dagger 破坏验证

MockK class verification breaking with Dagger

概览

预期:测试 Dagger 使用空构造函数创建的存储库class

问题:Mockk 验证与 mockkConstructorconfirmVerified 使用 Dagger 依赖注入创建存储库 class.

使用 Dagger's Android 构造函数模式实现存储库时,为了 Da​​gger 应用程序组件 [=126=,需要一个带有 @Inject 的空 constructor ],要知道要创建class.

confirmVerified 被注释掉时,测试通过。实施 confirmVerified 时,调用和跟踪先前测试的方法导致验证失败。

按预期工作 — 未创建 Dagger 的单例存储库

在单元测试中mockkObject用于模拟SomeRepository.kt对象,因为它是在Kotlin中手动创建的对象。

SomeRepository.kt

object SomeRepository {
    someMethod(...){...}  
}

SomeParamUnitTest.kt

@ExtendWith(SomeTestExtension::class)
class SomeParamUnitTest(val testDispatcher: TestCoroutineDispatcher) {

    private fun TestLoad() = teedLoadTestCases()
    private lateinit var someViewModel: SomeViewModel

    @BeforeAll
    fun beforeAll() {
        mockkObject(SomeRepository)
        ...
    }

    @AfterAll
    fun afterAll() {
        unmockkAll()
    }

    @ParameterizedTest
    @MethodSource("TestLoad")
    fun `Feed Load`(test: FeedLoadTest) = testDispatcher.runBlockingTest {
        mockComponents(test)
        someViewModel = SomeViewModel(...)
        assertContentList(...) // Working as expected.
        verifyTests(test)
    }

    private fun mockComponents(test: FeedLoadTest) {
        ...
        coEvery {
            SomeRepository.someMethod(test.isRealtime, any())
        } returns mockSomeMethod(test.mockFeedList, test.status)
        ...
    }

    private fun verifyTests(test: FeedLoadTest) {
        coVerify {
            SomeRepository.someMethod(test.isRealtime, any())
        }
        confirmVerified(SomeRepository)
    }
}

不工作 — confirmVerified 注入 Dagger 存储库

而不是在单元测试中对 SomeRepository.kt 使用 mockkObjectmockkConstructor 是根据 MockK 作者的 documentation 实现的因为 Dagger 注入的存储库有一个构造函数。另外,anyConstructed用于mockSomeRepository.kt的方法,监听被调用方法的验证。

实施

App.kt

class App : Application() {
    val appComponent = DaggerAppComponent.create()
    ...
}

AppComponent.kt

@Singleton
@Component
interface AppComponent {
    fun someRepository(): SomeRepository

    // The repository is injected inside of a fragment, then passed as a parameter into the ViewModel.
    fun inject(someFragment: SomeFragment)
}

SomeRepository.kt

@Singleton
class SomeRepository @Inject constructor() {
    someMethod(...){...}  
}

SomeParamUnitTest.kt

@ExtendWith(SomeTestExtension::class)
class SomeParamUnitTest(val testDispatcher: TestCoroutineDispatcher) {

    private fun TestLoad() = teedLoadTestCases()
    private lateinit var repository: SomeRepository
    private lateinit var someViewModel: SomeViewModel

    @BeforeAll
    fun beforeAll() {
        mockkConstructor(SomeRepository::class)
        ...
    }

    @AfterAll
    fun afterAll() {
        unmockkAll()
    }

    @ParameterizedTest
    @MethodSource("TestLoad")
    fun `Feed Load`(test: FeedLoadTest) = testDispatcher.runBlockingTest {
        repository = SomeRepository()
        mockComponents(test)
        someViewModel = SomeViewModel(...)
        assertContentList(...) // Working as expected.
        verifyTests(test)
    }

    private fun mockComponents(test: FeedLoadTest) {
        ...
        coEvery {
            anyConstructed<SomeRepository>().someMethod(test.isRealtime, any())
        } returns mockSomeMethod(test.mockFeedList, test.status)
        ...
    }

    private fun verifyTests(test: FeedLoadTest) {
        coVerify {
            anyConstructed<SomeRepository>().someMethod(test.isRealtime, any())
        }
        confirmVerified(repository)
    }
}

测试失败

对于运行的每个参数化测试,已验证和记录的调用计数都在累积增加。

java.lang.AssertionError: Verification acknowledgment failed

Verified call count: 368 Recorded call count: 57

Not verified calls:

Stack traces:

at io.mockk.impl.recording.CommonVerificationAcknowledger.acknowledgeVerified(CommonVerificationAcknowledger.kt:36) at io.mockk.MockKDsl.internalConfirmVerified(API.kt:272) at io.mockk.MockKKt.confirmVerified(MockK.kt:314) at app.coinverse.contentviewmodel.tests.FeedLoadTests.verifyTests(FeedLoadTests.kt:201) at app.coinverse.contentviewmodel.tests.FeedLoadTests.access$verifyTests(FeedLoadTests.kt:35) at app.coinverse.contentviewmodel.tests.FeedLoadTests$Feed Load.invokeSuspend(FeedLoadTests.kt:67) at app.coinverse.contentviewmodel.tests.FeedLoadTests$Feed Load.invoke(FeedLoadTests.kt) at kotlinx.coroutines.test.TestBuildersKt$runBlockingTest$deferred.invokeSuspend(TestBuilders.kt:50) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241) at kotlinx.coroutines.test.TestCoroutineDispatcher.dispatch(TestCoroutineDispatcher.kt:142) at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:423) at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26) at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109) at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:154) at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:89) at kotlinx.coroutines.BuildersKt.async(Unknown Source) at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:82) at kotlinx.coroutines.BuildersKt.async$default(Unknown Source) at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:49) at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:78) at app.coinverse.contentviewmodel.tests.FeedLoadTests.Feed Load(FeedLoadTests.kt:56) 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.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestTemplateMethod(TimeoutExtension.java:81) at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod[=38=](ExecutableInvoker.java:115) at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke[=38=](ExecutableInvoker.java:105) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35) 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(TestMethodTestDescriptor.java:202) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:135) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) at org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor.execute(NodeTestTask.java:198) at org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:138) at org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.lambda$execute(TestTemplateTestDescriptor.java:106) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:175) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:373) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:270) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:270) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:193) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:270) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:106) at org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:135) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at java.util.ArrayList.forEach(ArrayList.java:1257) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(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(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at java.util.ArrayList.forEach(ArrayList.java:1257) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(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(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) 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.DefaultLauncher.execute(DefaultLauncher.java:229) at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute(DefaultLauncher.java:197) at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128) at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69) 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)

尝试过的解决方案

1。将 mockkConstructorbeforeAll 移动到 beforeEach JUnit 5 测试生命周期方法。

这似乎是一个改进,因为已验证和记录的测试不会像以前那样累积每个参数化测试轮次。相反,未找到正确的方法签名。

测试失败

java.lang.AssertionError: Verification acknowledgment failed

Verified call count: 0 Recorded call count: 2

Not verified calls: 1) SomeRepository(mockkConstructor()).getMainFeedNetwork(false, Timestamp(seconds=1581980238, nanoseconds=352000000)) 2) SomeRepository(mockkConstructor()).getMainFeedRoom(Timestamp(seconds=1581980238, nanoseconds=352000000))

2。删除存储库的测试 class 级实例变量并通过方法参数传递存储库。

存储库作为每个参数化测试内部的新值创建,然后作为参数传递给 mockComponentsverifyTests

堆栈跟踪错误并不表示缺少任何方法。 未验证调用 为空。

测试失败

java.lang.AssertionError: Verification acknowledgment failed

Verified call count: 2 Recorded call count: 2

Not verified calls:

3。 Clear/unmock beforeEach 中的存储库使用 clearConstructorMockkunmockkConstructor

clearConstructorMockkunmockkConstructor 分别进行了测试,并收到与尝试的解决方案 #2 相同的失败消息。

使用 mockkClass() 模拟存储库 class 并存储为实例值

@ExtendWith(SomeTestExtension::class)
class SomeParamUnitTest(val testDispatcher: TestCoroutineDispatcher) {

    private fun TestLoad() = teedLoadTestCases()
    private val repository = mockkClass(SomeRepository::class)
    private lateinit var someViewModel: SomeViewModel

    @AfterAll
    fun afterAll() {
        unmockkAll()
    }

    @ParameterizedTest
    @MethodSource("TestLoad")
    fun `Feed Load`(test: FeedLoadTest) = testDispatcher.runBlockingTest {
        mockComponents(test)
        someViewModel = SomeViewModel(...)
        assertContentList(...) // Working as expected.
        verifyTests(test)
    }

    private fun mockComponents(test: FeedLoadTest) {
        ...
        coEvery {
            repository.someMethod(test.isRealtime, any())
        } returns mockSomeMethod(test.mockFeedList, test.status)
        ...
    }

    private fun verifyTests(test: FeedLoadTest) {
        coVerify {
            repository.someMethod(test.isRealtime, any())
        }
        confirmVerified(repository)
    }
}