代码之家  ›  专栏  ›  技术社区  ›  Erik

mock-在顶级val上调用mock/spy顶级扩展方法

  •  1
  • Erik  · 技术社区  · 6 年前

    在下面的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 -模型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: -
    
    2 回复  |  直到 6 年前
        1
  •  1
  •   oleksiyp    6 年前

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

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

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

    组合不起作用:

        verify { theVal.bar() }
    

    因为它也得到了验证。

    这也会起作用(如我所说 Foo 也是静态方法的第一个参数):

        mockkStatic("kot.TestFileKt")
    
        baz()
    
        verify { any<Foo>().bar() }
    
        2
  •  0
  •   Erik    6 年前

    不要使用初始化器,而是使用 backing (private) property 使用 get() 对于 val 被嘲弄:

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

    使用getter而不是initializer创建一个不带静态支持字段的getter方法。您可以检查字节码以查看:

    Kotlin:

    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
    

    你在这里能看到什么?两者之间有一个重要的相似之处 thisIsAField thisIsAValWithInitialiser ,因为它们是由静态字段支持的。吸气剂法 这是初始化器的值 只返回该值。价值是 private .

    两者之间的相似性 这是初始化器的值 thisIsAValWithGetter 它们都是公共getter方法,但区别在于 这是带着吸气剂的勇气 在方法体中硬编码。这只是mockk可以覆盖的一个公共方法(即使它是最终的)。

    我想(因为我不知道内部原因)莫克不能推翻 GETSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String; 这就是为什么 瓦尔 初始化程序无法模拟。