MockK - 在顶级 val 上调用模拟/间谍顶级扩展方法
MockK - mock / spy top level extension method being called on top level val
在下面的 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
.
如何在不更改顶级 属性 定义的情况下强制使用间谍?换句话说:我需要一个顶级 'constant',但我也想模拟它。我可以使用 val theVal get() = Foo()
,它可以解决问题,但它会显着改变代码,因为它每次都会替换 Foo
实例。
使用的版本:
- 科特林 1.3.10
-模拟K 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: -
不使用初始化程序,而是使用 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
在这里你能看到什么? thisIsAField
和 thisIsAValWithInitialiser
之间有一个重要的相似之处,即它们由静态字段支持。 thisIsAValWithInitialiser
的 getter 方法只是 return 那个值。值为 private
.
thisIsAValWithInitialiser
和thisIsAValWithGetter
的相同点在于都是publicgetter的方法,不同的是return的值是thisIsAValWithGetter
硬编码在方法体中。这只是 MockK 可以覆盖的 public 方法(即使它是最终的)。
我猜想(因为我不知道内部原理)MockK 不能否决 GETSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String;
,这就是为什么不能模拟 val
初始化程序的原因。
哦,当涉及到静态和对象 mockks 以及扩展函数时,这真是疯狂。为了生存,只需将扩展函数视为带有参数的静态函数。
检查,这是有效的,因为 fooInstance
只是一个作为第一个参数传递的对象:
mockkStatic("kot.TestFileKt")
baz()
val fooInstance = theVal
verify { fooInstance.bar() }
合并无效:
verify { theVal.bar() }
因为也验证过了
这也有效(正如我所说,Foo
只是静态方法的第一个参数):
mockkStatic("kot.TestFileKt")
baz()
verify { any<Foo>().bar() }
在下面的 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
.
如何在不更改顶级 属性 定义的情况下强制使用间谍?换句话说:我需要一个顶级 'constant',但我也想模拟它。我可以使用 val theVal get() = Foo()
,它可以解决问题,但它会显着改变代码,因为它每次都会替换 Foo
实例。
使用的版本: - 科特林 1.3.10 -模拟K 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: -
不使用初始化程序,而是使用 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
在这里你能看到什么? thisIsAField
和 thisIsAValWithInitialiser
之间有一个重要的相似之处,即它们由静态字段支持。 thisIsAValWithInitialiser
的 getter 方法只是 return 那个值。值为 private
.
thisIsAValWithInitialiser
和thisIsAValWithGetter
的相同点在于都是publicgetter的方法,不同的是return的值是thisIsAValWithGetter
硬编码在方法体中。这只是 MockK 可以覆盖的 public 方法(即使它是最终的)。
我猜想(因为我不知道内部原理)MockK 不能否决 GETSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String;
,这就是为什么不能模拟 val
初始化程序的原因。
哦,当涉及到静态和对象 mockks 以及扩展函数时,这真是疯狂。为了生存,只需将扩展函数视为带有参数的静态函数。
检查,这是有效的,因为 fooInstance
只是一个作为第一个参数传递的对象:
mockkStatic("kot.TestFileKt")
baz()
val fooInstance = theVal
verify { fooInstance.bar() }
合并无效:
verify { theVal.bar() }
因为也验证过了
这也有效(正如我所说,Foo
只是静态方法的第一个参数):
mockkStatic("kot.TestFileKt")
baz()
verify { any<Foo>().bar() }