【问题标题】:MockK - mock / spy top level extension method being called on top level valMockK - 在顶级 val 上调用模拟/间谍顶级扩展方法
【发布时间】:2018-11-27 14:35:58
【问题描述】:

在下面的 MWE 中,我试图验证调用 baz() 是否也会调用另一个对象的方法。但是,我似乎无法模拟/监视该对象。

MWE:

package com.example

import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.verify
import org.junit.jupiter.api.Test

class FooBarTest {
    @Test
    fun `top level fun baz() calls theVal_bar()`() {
        mockkStatic("com.example.FooBarTestKt")
        val spy = spyk(theVal, name = "Hello, Spy!")

        every { theVal } returns spy

        // Should call bar() on the spy, but note that the spy's name is not printed
        baz()

        verify { spy.bar() }
    }
}

class Foo

fun Foo.bar() = println("Foo.bar! name = $this")

val theVal = Foo()

fun baz() = theVal.bar()

这失败了,因为对theVal.bar() 的调用获得了val 初始化值而不是模拟值spy

如何在不更改顶级属性定义的情况下强制使用正在使用的间谍?换句话说:我需要一个顶级的“常量”,但我也想模拟它。我可以使用val theVal get() = Foo(),它解决了这个问题,但它显着改变了代码,因为它每次都会替换Foo 实例。

使用的版本: - 科特林 1.3.10 - MockK 1.8.13.kotlin13 - JUnit 5.3.1

错误:

java.lang.AssertionError: Verification failed: call 1 of 1: class com.example.FooBarTestKt.bar(eq(Foo(Hello, Spy!#1)))). Only one matching call to FooBarTestKt(static FooBarTestKt)/bar(Foo) happened, but arguments are not matching:
[0]: argument: com.example.Foo@476b0ae6, matcher: eq(Foo(Hello, Spy!#1)), result: -

【问题讨论】:

    标签: unit-testing kotlin mocking mockk


    【解决方案1】:

    哦,当涉及到静态和对象模拟以及扩展函数时,这真是太疯狂了。为了生存,只需将扩展函数视为带有参数的静态函数。

    检查,这是有效的,因为fooInstance 只是作为第一个参数传递的对象:

        mockkStatic("kot.TestFileKt")
    
        baz()
    
        val fooInstance = theVal
    
        verify { fooInstance.bar() }
    

    组合它不起作用:

        verify { theVal.bar() }
    

    因为它也是经过验证的。

    这也可以(正如我所说的 Foo 只是静态方法的第一个参数):

        mockkStatic("kot.TestFileKt")
    
        baz()
    
        verify { any<Foo>().bar() }
    

    【讨论】:

      【解决方案2】:

      不要使用初始化程序,而是使用 backing (private) property 并使用 get() 来模拟要模拟的 val

      private val _theVal = Foo()
      val theVal get() = _theVal
      

      使用 getter 代替初始化器会创建一个没有静态支持字段的 getter 方法。您可以检查字节码以查看:

      科特林:

      package com.example
      
      @JvmField // See also: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#instance-fields
      val thisIsAField = "I'm static!"
      
      val thisIsAValWithInitialiser = "I'm a static field too!"
      
      val thisIsAValWithGetter get() = "I'm hardcoded in the getter method!"
      

      字节码(我已经删除了很多杂乱无章的东西,以便我的观点更容易理解):

      public final static Ljava/lang/String; thisIsAField
      
      private final static Ljava/lang/String; thisIsAValWithInitialiser
      
      public final static getThisIsAValWithInitialiser()Ljava/lang/String;
      L0
      LINENUMBER 6 L0
      GETSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String;
      ARETURN
      L1
      
      public final static getThisIsAValWithGetter()Ljava/lang/String;
      L0
      LINENUMBER 8 L0
      LDC "I'm hardcoded in the getter method!"
      ARETURN
      L1
      
      static <clinit>()V
      L0
      LINENUMBER 4 L0
      LDC "I'm static!"
      PUTSTATIC com/example/FooBarTestKt.thisIsAField : Ljava/lang/String;
      L1
      LINENUMBER 6 L1
      LDC "I'm a static field too!"
      PUTSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String;
      RETURN
      

      你在这里能看到什么? thisIsAFieldthisIsAValWithInitialiser 之间有一个重要的相似之处,因为它们是由静态字段支持的。 thisIsAValWithInitialiser 的 getter 方法只返回该值。值为private

      thisIsAValWithInitialiserthisIsAValWithGetter的相似之处在于都是公共的getter方法,不同的是thisIsAValWithGetter的返回值是硬编码在方法体中的。这只是一个 MockK 可以覆盖的公共方法(即使它是最终的)。

      我猜(因为我不知道内部原理)MockK 不能推翻 GETSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String;,这就是为什么不能模拟 val 初始化程序的原因。

      【讨论】:

        猜你喜欢
        • 2018-03-29
        • 2017-07-24
        • 2018-05-15
        • 1970-01-01
        • 1970-01-01
        • 2019-01-26
        • 2019-01-09
        • 2015-06-09
        • 2020-03-31
        相关资源
        最近更新 更多