我正在使用Kotlin,Mockito和MockitoJRunner来运行以下简化测试:
open class SomeClassToBeMocked @Inject constructor() {
fun map(foo: Foo): Bar {...}
}
@Mock
private lateinit var someMock: SomeClassToBeMocked
@InjectMocks
private lateinit var subject: Subject
@Test
fun shouldAssertSomething() {
val foo = Foo() // from Foo.kt
val bar = Bar() // from Bar.java from *another module*
whenever(someMock.map(foo)).thenReturn(bar) // breakpoint[1]
subject.myMethod(foo)
verify(someMock).map(foo)
}
此模式适用于代码的其他部分,但不适用于此特定测试,我收到以下错误消息:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.
当我在breakpoint[1]
调试代码时,我可以看到someMock是类SomeClassToBeMocked$MockitoMock$
的一个实例。此外,如果我尝试在breakpoint[1]
之前立即调用someMock.map(foo)
,它确实运行原始方法,而不是像所有模拟一样返回null
。
对这里可能发生的事情有什么想法吗?
更新-1:我已经检查了错误消息中选项1的所有可能性。我也尝试了mock(SomeClassToBeMocked::class.java)
并收到相同的错误消息。对于一些最简单的类,这种情况始终发生,使用仅将对象 A 转换为对象 B 的单个公共方法。
UPDATE-2:如果有任何区别,我只是注意到,在发生这种情况的情况下,Bar()
与测试位于不同的模块中。我已经更新了代码以反映这一点。
UPDATE-3:如果我创建一个名为SomeClassToBeMocked
的接口并将原始类重命名为SomeClassToBeMockedImpl
,一切都像魅力一样工作。但是,我仍然想弄清楚为什么会发生这种情况以及如何避免为此创建接口。
为了使用 Mockito 模拟函数的返回值,您需要使用open
关键字对其进行标记:
open class SomeClassToBeMocked @Inject constructor() {
open fun map(foo: Foo): Bar {...}
}
问题是 Mockito 无法模拟最终(不能被覆盖)函数。与 Java 不同,Kotlin 需要对可覆盖成员进行显式注释。