MockK-在顶级val上调用模拟/间谍顶级扩展方法

时间:2018-11-27 14:35:58

标签: unit-testing kotlin mocking mockk

在下面的MWE中,我试图验证调用$ jupyter nbconvert --to html_embed Annex.ipynb [NbConvertApp] Converting notebook Annex.ipynb to html_embed /usr/local/lib/python3.6/site-packages/nbconvert/filters/datatypefilter.py:41: UserWarning: Your element with mimetype(s) dict_keys(['image/pdf']) is not able to be represented. mimetypes=output.keys()) [NbConvertApp] Writing 2624499 bytes to Annex.html 是否也在另一个对象上调用了一个方法。但是,我似乎无法对该对象进行模拟/监视。

MWE:

baz()

这失败了,因为对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()实例。

使用的版本:  -Kotlin 1.3.10  -MockK 1.8.13.kotlin13  -JUnit 5.3.1

错误:

Foo

2 个答案:

答案 0 :(得分:1)

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

检查,因为fooInstance只是作为第一个参数传递的对象,所以此方法有效:

    mockkStatic("kot.TestFileKt")

    baz()

    val fooInstance = theVal

    verify { fooInstance.bar() }

将其合并无效:

    verify { theVal.bar() }

因为它已经过验证。

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

    mockkStatic("kot.TestFileKt")

    baz()

    verify { any<Foo>().bar() }

答案 1 :(得分:0)

使用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之间的相似之处在于它们都是公共获取方法,而不同之处在于thisIsAValWithGetter的返回值被硬编码在方法主体中。这只是MockK可以覆盖的公共方法(即使它是最终的)。

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