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

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

在我看来,每个间谍用例都可以通过使用 callRealMethod 使用 mock 来处理。

我可以看到的一个区别是,如果您希望大多数方法调用都是真实的,那么使用 mock 对间谍可以节省一些代码行。是这样还是我错过了更大的画面?

141608 次浏览

答案就在 文件:

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

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

在发布1.8之前,span ()并没有产生真正的部分模拟,这让一些用户感到困惑。阅读更多有关谍报的内容: 给你或 javadoc 中的谍报(对象)方法。

callRealMethod()是在 spy()之后引入的,但是为了确保向下兼容,谍()被留在了那里。

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

如果有一个对象有8个方法,你有一个测试,你想调用7个实方法和存根一个方法,你有两个选项:

  1. 如果使用 mock,则必须通过调用7来设置它 CallRealMethod 和 stub one 方法
  2. 使用 spy,您必须通过使用一种方法来设置它

doCallRealMethod上的 正式文件建议使用间谍进行部分模拟。

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

间谍和模仿者的区别

当 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 ()方法时,您得到的 size 为1,但是 size ()方法没有被模拟!那么1是从哪里来的呢?内部 real size ()方法被调用,因为 size ()没有被模拟(或存根化) ,因此我们可以说条目被添加到了实际对象中。

资料来源: http://www.baeldung.com/mockito-spy + self note。

当您希望为 遗留代码创建单元测试时,间谍可能非常有用。

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

你可能不需要间谍,因为你可以模仿存款服务和提款服务。

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

    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();

在上面的链接中有更多的细节。

嘲笑对间谍

[测试双类型]

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();
}