Mockito - 间谍vs模拟

Abh*_*nav 73 java unit-testing mocking mockito spy

Mockito - 我理解间谍调用对象上的真实方法,而模拟调用double对象上的方法.除非有代码气味,否则应避免使用间谍.但是,间谍如何工作以及我何时应该使用它们?他们与嘲笑有什么不同?

Cra*_*ing 70

从技术上讲,"嘲笑"和"间谍"都是一种特殊的"测试双打".

不幸的是,Mockito的区别很奇怪.

mockito中的模拟是其他模拟框架中的普通模拟(允许您存根调用;即,从方法调用返回特定值).

mockito中的间谍是其他模拟框架中的部分模拟(对象的一部分将被模拟,部分将使用真实的方法调用).


小智 25

如果我们想模拟一个类的所有方法,则可以使用mock 。

如果我们想模拟某些方法,并且必须对其余方法进行实际调用,则可以使用间谍。


小智 21

两者都可以用于模拟方法或字段.不同之处在于,在模拟中,您正在创建一个完整的模拟或虚假对象,而在间谍中,有真实对象,您只是间谍或抄袭它的特定方法.

当然,在间谍对象中,因为它是一种真正的方法,当你没有对方法进行存根时,它会调用真正的方法行为.如果要更改并模拟方法,则需要将其存根.

考虑以下示例作为比较.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
 
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
 
    @Mock
    private List<String> mockList;
 
    @Spy
    private List<String> spyList = new ArrayList();
 
    @Test
    public void testMockList() {
        //by default, calling the methods of mock object will do nothing
        mockList.add("test");

        Mockito.verify(mockList).add("test");
        assertEquals(0, mockList.size());
        assertNull(mockList.get(0));
    }
 
    @Test
    public void testSpyList() {
        //spy object will call the real method when not stub
        spyList.add("test");

        Mockito.verify(spyList).add("test");
        assertEquals(1, spyList.size());
        assertEquals("test", spyList.get(0));
    }
 
    @Test
    public void testMockWithStub() {
        //try stubbing a method
        String expected = "Mock 100";
        when(mockList.get(100)).thenReturn(expected);
 
        assertEquals(expected, mockList.get(100));
    }
 
    @Test
    public void testSpyWithStub() {
        //stubbing a spy method will result the same as the mock object
        String expected = "Spy 100";
        //take note of using doReturn instead of when
        doReturn(expected).when(spyList).get(100);
 
        assertEquals(expected, spyList.get(100));
    }
}
Run Code Online (Sandbox Code Playgroud)

什么时候你使用模拟或间谍?如果你想要安全并避免调用外部服务,只想测试单元内部的逻辑,那么使用mock.如果你想调用外部服务并执行真正依赖的调用,或者只是说,你想按原样运行程序,只是存根特定方法,那么使用spy.这就是在mockito中间谍和模拟之间的区别.


Vis*_*tna 14

简而言之:

@Spy并且@Mock在代码测试中被大量使用,但开发人员在何时使用其中之一的情况下确实会混淆,因此开发人员最终使用@Mock是安全的。

  • 使用@Mock时,你只想测试功能 ,而不实际调用该方法。
  • @Spy当您想使用被调用的方法在外部 + 内部测试功能时使用。

下面是我在美国采用Election20xx场景的示例。

选民可以根据VotersOfBelow21和进行划分VotersOfABove21

The ideal Exit poll says that Trump will win the election because VotersOfBelow21and VotersOfABove21both will vote for trump saying " We elected President Trump "

但这不是真实的场景:

两个年龄段的选民都投票给特朗普,因为他们除了特朗普之外别无选择。

那你怎么测试呢??

public class VotersOfAbove21 {
public void weElected(String myVote){
  System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}
Run Code Online (Sandbox Code Playgroud)
public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}
Run Code Online (Sandbox Code Playgroud)
public class ElectionOfYear20XX {
  VotersOfAbove21 votersOfAbove21;
  VotersOfBelow21 votersOfBelow21;
  public boolean weElected(String WeElectedTrump){
    votersOfAbove21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");

    votersOfBelow21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");
    return true;
  }

}
Run Code Online (Sandbox Code Playgroud)

现在请注意,在上面的前两个类别中,这两个年龄段的人都说他们没有比特朗普更好的选择。这明确意味着他们投票给特朗普只是因为他们别无选择。

现在ElectionOfYear20XX 有人说特朗普赢了,因为两个年龄段的人都以压倒性优势投票给他。

如果我们ElectionOfYear20XX用@Mock测试,那么我们可能无法得到特朗普获胜的真正原因,我们将只是测试外部原因。

如果我们ElectionOfYear20XX使用@Spy 进行测试,那么我们就会得到特朗普凭借外部出口民意调查结果获胜的真正原因,即内部+外部。


我们ELectionOfYear20XX_Test班:

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Mock
  VotersOfBelow21 votersOfBelow21;
  @Mock
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}
Run Code Online (Sandbox Code Playgroud)

这应该只输出逻辑测试结果,即外部检查:

We elected President Trump 
We elected President Trump 
Run Code Online (Sandbox Code Playgroud)

@Spy使用实际方法调用在外部和内部进行测试。

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Spy
  VotersOfBelow21 votersOfBelow21;
  @Spy
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}
Run Code Online (Sandbox Code Playgroud)

输出:

Voters of above 21 has no Choice Than Thrump in 20XX 
We elected President Trump 
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump 
Run Code Online (Sandbox Code Playgroud)

  • 怎样解释 (2认同)

Jai*_*ide 11

最好的起点可能是mockito的文档.

一般来说,mockito mock允许您创建存根.

例如,如果该方法执行昂贵的操作,则可以创建存根方法.比如说,它获取数据库连接,从数据库中检索一个值并将其返回给调用者.获取数据库连接可能需要30秒,这会将测试执行速度降低到可能上下文切换(或停止运行测试)的程度.

如果您正在测试的逻辑不关心数据库连接,那么您可以使用返回硬编码值的存根替换该方法.

mockito spy允许您检查方法是否调用其他方法.在尝试获取遗留代码时,这非常有用.

如果您正在测试通过副作用工作的方法,那么您将使用模拟间谍.这会委托对真实对象的调用,并允许您验证方法调用,调用的次数等.


Sur*_*oen 11

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

您可能不需要间谍,因为您可以只模拟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);
    }
Run Code Online (Sandbox Code Playgroud)

是的,您可以更改为第一个代码,但随后会更改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();
    }
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样使用spy注入依赖:

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();
Run Code Online (Sandbox Code Playgroud)

上面链接中的更多细节.


del*_*bao 11

TL; DR版本,

使用mock,它会为您创建一个裸骨shell实例.

List<String> mockList = Mockito.mock(ArrayList.class);
Run Code Online (Sandbox Code Playgroud)

使用间谍,您可以部分模拟现有实例

List<String> spyList = Mockito.spy(new ArrayList<String>());
Run Code Online (Sandbox Code Playgroud)

Spy的典型用例:该类具有参数化构造函数,您希望首先创建该对象.


小智 8

节目有点晚了,但我觉得其他回复并没有很好地说明间谍和模拟之间的差异。所以这是一个小演示

给定一个要测试的服务

public class EmailService {

public String sendMail(String email) {
    return String.format("Email successfully sent to %s", email);
   }
}
Run Code Online (Sandbox Code Playgroud)

我们现在可以用四种不同的场景进行一些测试。

  1. 调用并存根模拟
  2. 无需存根即可调用模拟
  3. 无需存根即可调用间谍
  4. 打电话并打断间谍

设置:

private final String testEmail = "randomuser@domain.com";
private final String success = "SUCCESS";
@Mock EmailService emailService;
@Spy EmailService emailServiceSpy;
Run Code Online (Sandbox Code Playgroud)

测试:

@Test
@Description("When mock is called, we can return any response we like")
public void simpleTest1() {

    when(emailService.sendMail(testEmail)).thenReturn(success);
    assertEquals(success, emailService.sendMail(testEmail));
}

@Test
@Description("When mock is called but not stubbed, we receive a null value")
public void simpleTest2() {
    assertNull(emailService.sendMail(testEmail));
}

@Test
@Description("When a spy is called but not stubbed, the concrete impl is called")
public void simpleTest3() {
    assertTrue(emailServiceSpy.sendMail(testEmail).contains(testEmail));
}

@Test
@Description("When a spy is called and stubbed, stubbed value is returned")
public void simpleTest4() {
    when(emailServiceSpy.sendMail(testEmail)).thenReturn(success);
    assertEquals(success, emailServiceSpy.sendMail(testEmail));
}
Run Code Online (Sandbox Code Playgroud)

如果没有存根,Mock 将返回 null 值,而如果没有存根,Spy 将调用具体类内部的实现方法。


leo*_*o9r 7

我喜欢这个建议的简单性:

  • 如果您想安全并避免调用外部服务,而只想测试单元内部的逻辑,请使用mock
  • 如果您想调用外部服务并执行实际依赖项的调用,或者简单地说,您想按原样运行程序并仅存根特定方法,则使用spy

来源:https : //javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

一个常见的区别是:

  • 如果您想直接存根依赖项的方法,则Mock该依赖项。
  • 如果您想存根依赖项中的数据,以便其所有方法都返回您需要的测试值,则可以监视该依赖项。