【问题标题】:MockK class verification breaking with Dagger用 Dagger 打破 MockK 类验证
【发布时间】:2020-06-02 21:11:35
【问题描述】:

概述

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

问题:Mockk 验证正在与mockkConstructorconfirmVerified 一起使用 Dagger 依赖注入创建存储库类。

当使用Dagger's Android 构造函数模式实现存储库时,需要一个空的constructor@Inject,以便Dagger 应用程序组件AppComponent.kt 知道创建类.

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 用于模拟 SomeRepository.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: 验证确认失败

已验证的通话次数:368 通话记录数:57

未验证的调用:

堆栈跟踪:

在 io.mockk.impl.recording.CommonVerificationAcknowledger.acknowledgeVerified(CommonVerificationAcknowledger.kt:36) 在 io.mockk.MockKDsl.internalConfirmVerified (API.kt:272) 在 io.mockk.MockKKt.confirmVerified(MockK.kt:314) 在 app.coinverse.contentviewmodel.tests.FeedLoadTests.verifyTests(FeedLoadTests.kt:201) 在 app.coinverse.contentviewmodel.tests.FeedLoadTests.access$verifyTests(FeedLoadTests.kt:35) 在 app.coinverse.contentviewmodel.tests.FeedLoadTests$Feed Load$1.invokeSuspend(FeedLoadTests.kt:67) 在 app.coinverse.contentviewmodel.tests.FeedLoadTests$Feed Load$1.invoke(FeedLoadTests.kt) 在 kotlinx.coroutines.test.TestBuildersKt$runBlockingTest$deferred$1.invokeSuspend(TestBuilders.kt:50) 在 kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 在 kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241) 在 kotlinx.coroutines.test.TestCoroutineDispatcher.dispatch(TestCoroutineDispatcher.kt:142) 在 kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:423) 在 kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26) 在 kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109) 在 kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:154) 在 kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:89) 在 kotlinx.coroutines.BuildersKt.async(未知来源) 在 kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:82) 在 kotlinx.coroutines.BuildersKt.async$default(未知来源) 在 kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:49) 在 kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:78) 在 app.coinverse.contentviewmodel.tests.FeedLoadTests.Feed 负载(FeedLoadTests.kt:56) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675) 在 org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) 在 org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125) 在 org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132) 在 org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124) 在 org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestTemplateMethod(TimeoutExtension.java:81) 在 org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) 在 org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) 在 org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104) 在 org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62) 在 org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43) 在 org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35) 在 org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) 在 org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) 在 org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198) 在 org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135) 在 org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) 在 org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) 在 org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask$DefaultDynamicTestExecutor.execute(NodeTestTask.java:198) 在 org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:138) 在 org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.lambda$execute$2(TestTemplateTestDescriptor.java:106) 在 java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) 在 java.util.stream.ReferencePipeline$11$1.accept(ReferencePipeline.java:373) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) 在 java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580) 在 java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:270) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) 在 java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) 在 java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) 在 java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) 在 java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) 在 java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 在 java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) 在 java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:270) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) 在 java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) 在 java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) 在 java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) 在 java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) 在 java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 在 java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) 在 java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:270) 在 java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) 在 java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) 在 java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) 在 java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) 在 java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) 在 java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 在 java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) 在 org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:106) 在 org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:41) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) 在 org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) 在 java.util.ArrayList.forEach(ArrayList.java:1257) 在 org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) 在 org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) 在 java.util.ArrayList.forEach(ArrayList.java:1257) 在 org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) 在 org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) 在 org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) 在 org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) 在 org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) 在 org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229) 在 org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197) 在 org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211) 在 org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191) 在 org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128) 在 com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69) 在 com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) 在 com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) 在 com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

尝试的解决方案

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

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

测试失败

java.lang.AssertionError: 验证确认失败

已验证的通话次数:0 通话记录数:2

未验证的调用: 1) SomeRepository(mockkConstructor()).getMainFeedNetwork(false, Timestamp(seconds=1581980238, nanoseconds=352000000)) 2) SomeRepository(mockkConstructor()).getMainFeedRoom(Timestamp(seconds=1581980238, nanoseconds=352000000))

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

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

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

测试失败

java.lang.AssertionError: 验证确认失败

已验证的通话次数:2 通话记录数:2

未验证的调用:

3。使用clearConstructorMockkunmockkConstructor 清除/取消模拟beforeEach 中的存储库。

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

【问题讨论】:

    标签: android dagger-2 dagger mockk daggermock


    【解决方案1】:

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

    @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)
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-10-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多