2

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.

Nishant Shah
  • 3,442
  • 5
  • 25
  • 34
  • So from my understanding of the question you want to set the property `customerData` with a specified value at the start of the test right? I think what you're _actually_ currently doing is tell mockk that when you run the test and the property is set to that data, continue. – Ed Holloway-George Aug 09 '21 at 16:29
  • As far as I know, it isn't possible to mock private variables with mockk - you can mock private methods though. Maybe consider using the `@VisibleForTesting` annotation – Ed Holloway-George Aug 09 '21 at 16:32
  • https://stackoverflow.com/questions/51316105/mock-a-private-property Personally i think is ok if you need to call the update method or to inject the mutableList by constructor – Manuel Mato Aug 13 '21 at 20:12

1 Answers1

2

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()
    }
}
S Haque
  • 6,881
  • 5
  • 29
  • 35
kingston
  • 11,053
  • 14
  • 62
  • 116
  • Is there any other way without changing the implementation? – Nishant Shah Aug 15 '21 at 14:04
  • @NishantShah as suggested you can declare the field as public and tag it with @VisibleForTesting. `VisibleForTesting` is a smell. It normally means that you are doing something wrong and in your case, you know what: you are not injecting a dependency. It doesn't look a big change to me so I would implement it properly. But maybe this for you was just an example and you have a lot of legacy code. If not, even if there was a way to do it I would implement it properly – kingston Aug 15 '21 at 19:15
  • 1
    @NishantShah https://stackoverflow.com/questions/61786233/mockk-mocking-private-properties-in-kotlin – kingston Aug 16 '21 at 10:51