【发布时间】:2022-10-12 21:39:53
【问题描述】:
在处理一些遗留测试时,我最近发现了 Mockito 及其间谍的一些意外行为。考虑以下类(特别注意SomeInterface 的匿名嵌套实现)
public class ClassUnderTest {
private String name = "initial value";
private final SomeInterface impl = new SomeInterface() {
@Override
public void foo(String name) {
// the following call "unwraps" the spied object and directly calls internalFoo on the "raw" object but NOT on
// the spy (method is called on the "toBeSpied" object from testObjInstantiation and not on the "spy" instance)
internalFoo(name);
}
};
private final class SomeClass {
private void foo(String name) {
// works as expected when using a nested class (called on the spy)
internalFoo(name);
}
}
public void foo(String name) {
impl.foo(name);
}
public void bar(String name) {
internalFoo(name);
}
public void baz(String name) {
new SomeClass().foo(name);
}
public String getName() {
return name;
}
private void internalFoo(String name) {
this.name = name;
}
private interface SomeInterface {
void foo(String name);
}
}
进一步考虑以下测试:
@Test
void testObjInstantiation() {
final var toBeSpied = new ClassUnderTest();
final var spy = Mockito.spy(toBeSpied);
spy.bar("name set on spy via bar");
Assertions.assertEquals("name set on spy via bar", spy.getName());
spy.baz("name set on spy via baz");
Assertions.assertEquals("name set on spy via baz", spy.getName());
spy.foo("name set on spy via foo");
Assertions.assertEquals("name set on spy via foo", spy.getName()); // this fails Expected: name set on spy via foo Actual: name set on spy via baz
}
我希望所有断言都能成功。然而,最后一个失败了。原因是spy.foo 通过SomeInterface 实现(impl 成员)使用“间接”。此时,间谍对象被“展开”。从impl 调用的internalFoo 是不是不再调用间谍,而是调用“原始”对象。基本上它是在测试用例的toBeSpied 实例上调用的,并且不是在spy 实例上。
使用嵌套类时,一切都按预期工作(参见ClassUnderTest.baz,它实例化了SomeClass 对象)。
考虑以下测试:
@Test
void testClassInstantiation() {
final var spy = Mockito.spy(ClassUnderTest.class);
spy.bar("name set on spy via bar");
Assertions.assertEquals("name set on spy via bar", spy.getName());
spy.baz("name set on spy via baz");
Assertions.assertEquals("name set on spy via baz", spy.getName());
spy.foo("name set on spy via foo");
Assertions.assertEquals("name set on spy via foo", spy.getName());
}
唯一的区别是使用Mockito.spy 的Class<T> 重载而不是Mockito.spy 的对象间谍方法T。在这种情况下,所有断言都成功。
使用 Mockito v3.3.3 和 v4.7.0(撰写此问题时 Mockito 的最新版本)可以观察到相同的行为。
- 这是预期的行为吗?如果是,这是什么原因?
- 是否有关于这种行为的文档?
- 如果需要使用间谍(即由于遗留测试)并且没有可用的默认构造函数,如何避免这种行为?
【问题讨论】: