间谍实例的意义何在?

时间:2016-05-28 18:18:59

标签: mockito

我正在使用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成功,因为我试图从实际对象而不是间谍中读取数据。因此,如果间谍不与传递的对象进行交互,为什么它会接受一个,为什么它会在模拟设置中保留它?

1 个答案:

答案 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()),其中开发人员不会保留对间谍原始对象的引用。这可以帮助避免混淆与哪个实例进行交互,我强烈推荐它。