我正在使用spy
调试测试,发现一些令人困惑的事情。
public class SpyTest {
@Test
public void testSpy1(){
Thing thing = new Thing();
Thing thingSpy = Mockito.spy(thing);
thingSpy.modify("bar");
assertThat(thing.getFoo(), is("bar"));
}
@Test
public void testSpy2(){
Thing thing = new Thing();
Thing thingSpy = Mockito.spy(thing);
thingSpy.modify("bar");
assertThat(thingSpy.getFoo(), is("bar"));
}
private static class Thing {
private String foo = "";
public void modify(String foo){
this.foo = foo;
}
public String getFoo() {
return foo;
}
}
}
我希望间谍能够推迟传递的实例。
testSpy1
失败并且testSpy2
成功,因为我试图从实际对象而不是间谍中读取数据。因此,如果间谍不与传递的对象进行交互,为什么它会接受一个,为什么它会在模拟设置中保留它?
答案 0 :(得分:3)
间谍制作传递对象的字段的浅表副本。模拟不使用普通的构造函数或初始化器,因此在正常的Mockito模拟中,所有字段都将保持默认状态(0,null,false等)。这可能使得很难在模拟上调用实际方法,因为许多实例方法与它们的某些实例的状态交互,而未初始化的字段通常是无效的。通过允许正常构造,然后复制状态,间谍对象的行为几乎与普通对象完全相同 - 除了Mockito提供的存根和验证功能。
虽然您可以使用正常的Mockito创建的模拟并设置其字段,但这往往会破坏封装,并且无论如何都不会轻易设置私有或最终字段。使用当前的间谍语法,您可以使用您选择的参数调用您选择的构造函数,并尽可能准确地设置您的类。
但为什么不保留旧对象并委托给它呢?答案是this
:
class AnotherThing {
String greet() {
return "Hello " + getName() + "!";
}
String getName() {
return "World";
}
}
class AnotherThingTest {
void testOverrideName() {
AnotherThing anotherThing = new AnotherThing();
AnotherThing spyOfAnotherThing = spy(anotherThing);
doReturn("Dolly").when(spyOfAnotherThing).getName();
assertEquals("Hello Dolly!", spyOfAnotherThing.greet()); // fails!
}
}
上述内容似乎是一个合理的测试,但如果Mockito要委托给您的AnotherThing实例,则会失败:spyOfAnotherThing
会委托给anotherThing
,这会调用getName
(明确本身上的this.getName()
),它将返回默认的“世界”。为了使部分模拟工作,间谍实现和原始实现之间this
的值必须相同,这需要一个副本。
出于这个原因,您经常会看到spy(new ClassToSpy())
,其中开发人员不会保留对间谍原始对象的引用。这可以帮助避免混淆与哪个实例进行交互,我强烈推荐它。