Mockk set private properties of collection in Kotlin

Viewed 1113

While accessing the private variables in Mockk, not able to set the values of private property.

We have CustomerImpl class and which has 1 private property called customerData.

We want to set data in private property customerData from our Test case without calling updateCache() method.

We are using Mockk 1.10.6

CustomerImpl Class

class CustomerImpl {
    private var customerData : MutableList<CustomerModel> = mutableListOf()

    private fun updateCache() {
        ... here customerData is updated.
    }

    fun clearCache() {
        if(customerData.isNotEmpty())
            customerData.clear()
    }
}

Test Class

class CustomerImplTest {

    private lateinit var customerImpl: CustomerImpl

    @Before
    fun setUp() {

        MockKAnnotations.init(this)

        customerImpl = spyk(CustomerImpl(), recordPrivateCalls = true)
    }

    @Test
    fun clearCacheTest() {
        val customer = listOf(CustomerModel(id = "3", name = "John"))
        every { customerImpl setProperty "customer" value customer } just Runs

        customerImpl.clearCache()
    }
}

When we tried to run this test case, it is giving us below error.

Exception

io.mockk.MockKException: Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock

    at io.mockk.impl.recording.states.StubbingState.checkMissingCalls(StubbingState.kt:14)
    at io.mockk.impl.recording.states.StubbingState.recordingDone(StubbingState.kt:8)
    at io.mockk.impl.recording.CommonCallRecorder.done(CommonCallRecorder.kt:47)
    at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:60)
    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:98)
    at com.xyz.impl.CustomerImplTest.clearCacheTest(CustomerImplTest.kt:60)
    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.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    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.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

We are looking for solution in Mockk only.

1 Answers

If you want to be able to test all the scenarios with your class, you should inject all the dependencies and not create objects internally.

As already suggested you need to pass the list as a constructor parameter.

class CustomerImpl(
    private var customerData : MutableList<CustomerModel> = mutableListOf()
) {    
    private fun updateCache() {
        // Here customerData is updated.
    }

    fun clearCache() {
        if(customerData.isNotEmpty())
            customerData.clear()
    }
}

The real consumer of the class will work in the same way but you will be able to inject the dependency. If you are worried about exposing in some way the customerData consider that you can/should have a sort of factory to build the instance without knowing the concrete class. So for example something like:

interface Customer {
    fun clearCache()

    companion object {
        fun newInstance(): Customer = CustomerImpl()
    }
}

class CustomerImpl(
    private var customerData : List<CustomerModel> = mutableListOf()
): Customer {    
    private fun updateCache() {
        // Here customerData is updated.
    }

    override fun clearCache() {
        if(customerData.isNotEmpty())
            (customerData as? MutableList)?.clear()
    }
}
Related