使用Mockito时,嘲笑和间谍有什么区别?


137

使用Mockito间谍的用例是什么?

在我看来,每个间谍用例都可以使用callRealMethod进行模拟处理。

我可以看到的一个区别是,如果您希望大多数方法调用都是真实的,则可以节省一些代码行以使用模拟与间谍。是这样还是我错过了更大的前景?

Answers:


100

答案在文档中

真正的部分模拟(自1.8.0开始)

最后,在邮件列表上进行了许多内部辩论和讨论之后,Mockito添加了部分模拟支持。以前,我们将部分模拟视为代码气味。但是,我们发现了部分模拟的合法用例。

在1.8版之前,spy()并未产生真正的部分模拟,并且对于某些用户而言令人困惑。阅读更多有关间谍的信息:此处或在javadoc中获取spy(Object)方法。

callRealMethod()是在之后引入的spy(),但为了确保向后兼容,当然保留了spy()。

否则,您是对的:间谍的所有方法都是真实的,除非被阻止。除非callRealMethod()调用,否则模拟的所有方法都是存根的。通常,我更喜欢使用callRealMethod(),因为它不会强迫我使用doXxx().when()成语而不是传统的习惯用法。when().thenXxx()


在这些情况下,首选模拟而不是间谍的问题是,当类使用未注入成员(但在本地初始化)的成员,并随后由“真实”方法使用时;在模拟中,成员将被初始化为其默认Java值,这可能会导致错误的行为,甚至会导致NullPointerException。传递此方法的方法是添加一个“ init”方法,然后“真正”调用它,但这对我来说似乎有点过头了。
Eyal Roth

在文档中:“应该谨慎偶尔使用间谍,例如在处理遗留代码时。” 单元测试空间有太多做同一件事的方法。
gdbj

89

间谍与模拟之间的区别

当Mockito创建模拟时–它是从类型的类而不是实际实例中进行的。该模拟仅创建Class的准系统shell实例,完全可以跟踪与之的交互。另一方面,间谍将包装现有实例。它的行为仍与普通实例相同–唯一的区别是,它还将被用来跟踪与其进行的所有交互。

在下面的示例中,我们创建ArrayList类的模拟:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

如您所见–将元素添加到模拟列表实际上并没有添加任何内容–只是调用该方法而没有其他副作用。另一方面,间谍的行为会有所不同–它实际上将调用add方法的实际实现并将该元素添加到基础列表中:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

在这里,我们可以肯定地说,调用了对象的真正内部方法是因为当您调用size()方法时,您获得的大小为1,但是没有对这个size()方法进行模拟!那么1来自哪里呢?内部的实际size()方法被称为,因为size()没有被模拟(或存根),因此我们可以说该条目已添加到真实对象中。

资料来源:http : //www.baeldung.com/mockito-spy +自我说明。


1
您不是说size()返回1吗?
黑色

在第一个示例中,如果该方法也未被取消,为什么还要mockedList.size()返回0?给定方法的返回类型,这仅仅是默认值吗?
mike

@mike:在Java中mockedList.size()返回int,默认值为int0。如果您尝试assertEquals(0, mockedList.size());在之后执行mockedList.clear();,结果将保持不变。
realPK '17

2
这个答案写得很好,很简单,可以帮助我最终理解模拟与间谍之间的区别。好一个。
佩萨

38

如果有一个带有8个方法的对象,并且您有一个测试要在其中调用7个真实方法并将其存根,则有两个选择:

  1. 使用模拟,您必须通过调用7 callRealMethod并将其存根到一个方法来进行设置
  2. 使用,spy您必须通过存根一种方法进行设置

官方文档doCallRealMethod建议使用间谍的部分嘲笑。

另请参见javadoc spy(Object)以了解有关部分模拟的更多信息。Mockito.spy()是创建部分模拟的推荐方法。原因是它保证针对正确构造的对象调用真实方法,因为您负责构造传递给spy()方法的对象。


5

当您要为遗留代码创建单元测试时,间谍可能会很有用。

我在这里https://www.surasint.com/mockito-with-spy/创建了一个可运行的示例,在此复制了一些示例。

如果您有类似以下代码的内容:

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

您可能不需要间谍,因为您可以仅模拟DepositMoneyService和WithdrawMoneyService。

但是对于一些遗留代码,依赖关系就在这样的代码中:

    public void transfer(String fromAccount, String toAccount, double amount){

        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

是的,您可以更改为第一个代码,但随后会更改API。如果许多地方都使用此方法,则必须全部更改。

另一种选择是,您可以像这样提取依赖项:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }
    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

然后可以使用间谍注入依赖项,如下所示:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target).proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target).proxyWithdrawMoneyServiceCreator();

在上面的链接中有更多详细信息。


0

Mock是一个仅有的双重对象。该对象具有相同的方法签名,但实现为空,并返回默认值-0和null

Spy是一个克隆的双重对象。基于实际对象克隆了新对象,但是您可以对其进行模拟

class A {

    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() {
        foo4();
    }

    void foo4() {

    }
}
@Test
public void testMockA() {

    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("MockedString");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals(null, result2);

    //Case 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("MockedString");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals("RealString_2", result2);

    //Case 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}

[测试双重类型]

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.