【问题标题】:Mockk set private properties of collection in KotlinMockk 在 Kotlin 中设置集合的私有属性
【发布时间】:2021-08-07 10:05:54
【问题描述】:

在 Mockk 中访问私有变量时,无法设置私有属性的值。

我们有 CustomerImpl 类,它有 1 个名为 customerData 的私有属性。

我们希望在不调用 updateCache() 方法的情况下从我们的测试用例中设置私有属性 customerData 中的数据。

我们正在使用 Mockk 1.10.6

CustomerImpl 类

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

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

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

测试类

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

当我们尝试运行这个测试用例时,它给了我们以下错误。

例外

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)

我们只在 Mockk 中寻找解决方案。

【问题讨论】:

  • 所以根据我对问题的理解,您想在测试开始时将属性customerData 设置为指定值吗?我认为您当前实际上所做的是告诉 mockk,当您运行测试并且属性设置为该数据时,继续。
  • 据我所知,用 mockk 模拟私有变量是不可能的——不过你可以模拟私有方法。也许考虑使用@VisibleForTesting 注释
  • stackoverflow.com/questions/51316105/mock-a-private-property 我个人认为如果你需要调用更新方法或通过构造函数注入 mutableList 是可以的

标签: android kotlin junit4 mockk


【解决方案1】:

如果您希望能够使用您的类测试所有场景,您应该注入所有依赖项,而不是在内部创建对象。

如前所述,您需要将列表作为构造函数参数传递。

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

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

该类的真正使用者将以相同的方式工作,但您将能够注入依赖项。如果您担心以某种方式暴露customerData,请考虑您可以/应该有一种工厂来构建实例而不知道具体类。例如:

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

【讨论】:

  • 在不改变实现的情况下还有其他方法吗?
  • @NishantShah 正如建议的那样,您可以将该字段声明为公共并使用@VisibleForTesting 对其进行标记。 VisibleForTesting 是一种气味。这通常意味着您做错了什么,在您的情况下,您知道什么:您没有注入依赖项。对我来说这看起来没什么大的变化,所以我会正确地实施它。但也许这对你来说只是一个例子,你有很多遗留代码。如果没有,即使有办法做到这一点,我也会正确实施它
猜你喜欢
  • 2020-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多