162

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

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

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

4

5 回答 5

118

间谍和模拟之间的区别

当 Mockito 创建一个模拟时——它是从一个类型的类中创建的,而不是从一个实际的实例中创建的。mock 只是创建了 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() 方法时,你得到的 size 为 1,但是这个 size() 方法并没有被模拟!那么 1 是从哪里来的呢?内部实际 size() 方法被称为 size() 未被模拟(或存根),因此我们可以说该条目已添加到真实对象中。

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

于 2016-08-29T00:27:19.750 回答
113

答案在文档中:

真正的部分模拟(自 1.8.0 起)

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

在 1.8 版本之前,spy() 没有产生真正的部分模拟,这让一些用户感到困惑。阅读有关间谍的更多信息:此处或在 javadoc 中用于 spy(Object) 方法。

callRealMethod()在 之后引入spy(),但 spy() 当然被留在那里,以确保向后兼容。

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

于 2013-02-24T16:09:38.370 回答
41

如果有一个具有 8 个方法的对象,并且您有一个要调用 7 个真实方法和 stub 一个方法的测试,那么您有两个选择:

  1. 使用模拟,您必须通过调用 7 callRealMethod 和存根一个方法来设置它
  2. 使用spy你必须通过存根一种方法来设置它

官方文档建议doCallRealMethod使用 spy 进行部分模拟。

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

于 2014-05-28T13:35:28.607 回答
9

当您想为遗留代码创建单元测试时,Spy 会很有用。

我在这里创建了一个可运行的示例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();

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

于 2017-12-09T15:22:24.017 回答
5

[测试双重类型]

Mock对比Spy

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();
}
于 2020-06-24T12:35:46.470 回答