Expected: Testing Dagger created repository class with empty constructor
Issue: Mockk verification is breaking with mockkConstructor
and confirmVerified
using Dagger dependency injection to create a repository class.
When implementing a repository with Dagger's Android constructor pattern, an empty constructor
with @Inject
is required in order for the Dagger app component, AppComponent.kt, to know to create the class.
When confirmVerified
is commented out, the tests pass. When confirmVerified
is implemented, the methods from prior tests are being called and tracked causing verification to fail.
In the unit test mockkObject
is used to mock SomeRepository.kt object because it is an object created manually in 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
with injected Dagger repositoryInstead of using mockkObject
for SomeRepository.kt in the unit test, mockkConstructor
is implemented per the MockK author's documentation because the Dagger injected repository has a constructor. In addition, anyConstructed
is used to mock SomeRepository.kt's methods and listen for verification of the methods being called.
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)
}
}
For each parameterized test that runs, the verified and recorded call counts are cumulatively increasing.
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$1.invokeSuspend(FeedLoadTests.kt:67) at app.coinverse.contentviewmodel.tests.FeedLoadTests$Feed Load$1.invoke(FeedLoadTests.kt) at kotlinx.coroutines.test.TestBuildersKt$runBlockingTest$deferred$1.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$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: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$6(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$5(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$7(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$8(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$2(TestTemplateTestDescriptor.java:106) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.util.stream.ReferencePipeline$11$1.accept(ReferencePipeline.java:373) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.util.stream.ReferencePipeline$3$1.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$7$1.accept(ReferencePipeline.java:270) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline$3$1.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$7$1.accept(ReferencePipeline.java:270) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.stream.ReferencePipeline$3$1.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$7$1.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$5(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$7(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$8(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$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:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(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$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:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(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$6(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)
mockkConstructor
from beforeAll
to beforeEach
JUnit 5 test lifecycle method.This seems to be an improvement as the verified and recorded tests are not accumulating each parameterized test round like before. Rather, the correct method signature is not being found.
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))
The repository is created as a new value inside of each parameterized test, then passed as a parameter to mockComponents
and verifyTests
.
The stack trace errors do not indicate any methods are missing. Not verified calls is empty.
java.lang.AssertionError: Verification acknowledgment failed
Verified call count: 2 Recorded call count: 2
Not verified calls:
beforeEach
using clearConstructorMockk
and unmockkConstructor
.clearConstructorMockk
and unmockkConstructor
were tested individually and received the same failure messages as Attempted Solution #2.
mockkClass()
and store as instance value@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)
}
}