138

Mockito - I understand a spy calls the real methods on an object, while a mock calls methods on the double object. Also spies are to be avoided unless there is a code smell. However, how do spies work and when should i actually use them? How are they different from mocks?

4

8 回答 8

123

从技术上讲,“模拟”和“间谍”都是一种特殊的“测试替身”。

不幸的是,Mockito 使这种区别变得很奇怪。

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

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

于 2015-02-03T16:20:21.663 回答
90

两者都可以用来模拟方法或字段。不同之处在于,在 mock 中,您正在创建一个完整的 mock 或假对象,而在 spy 中,存在真实对象,而您只是监视或存根它的特定方法。

当然,在 spy 对象中,因为它是一个真实的方法,所以当你没有对方法进行存根时,它会调用真实的方法行为。如果要更改和模拟该方法,则需要对它进行存根。

考虑下面的示例作为比较。

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

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

于 2017-12-19T15:22:52.203 回答
21

TL;DR 版本,

使用mock,它会为您创建一个简单的 shell 实例。

List<String> mockList = Mockito.mock(ArrayList.class);

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

List<String> spyList = Mockito.spy(new ArrayList<String>());

Spy 的典型用例:类有一个参数化的构造函数,你想先创建对象。

于 2017-08-28T19:41:54.563 回答
19

简而言之:

@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 ");
}
}

public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}

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

}

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

现在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"));
  }

}

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

We elected President Trump 
We elected President Trump 

@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"));
  }

}

输出:

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 
于 2020-01-27T12:05:54.360 回答
17

我在这里创建了一个可运行的示例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-04-02T19:06:09.470 回答
15

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

一般来说,mockito 模拟允许您创建存根。

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

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

mockito spy 可以让你检查一个方法是否调用了其他方法。这在尝试测试遗留代码时非常有用。

如果您正在测试一种通过副作用起作用的方法,那么您将使用 mockito 间谍,这很有用。这将调用委托给真实对象,并允许您验证方法调用、调用次数等。

于 2015-02-03T10:06:36.143 回答
9

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

  • 如果您想安全并避免调用外部服务并且只想测试单元内部的逻辑,请使用mock
  • 如果你想调用外部服务并调用真正的依赖,或者简单地说,你想按原样运行程序并且只是存根特定的方法,那么使用spy

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

一个常见的区别是:

  • 如果您想直接存根依赖项的方法,请模拟该依赖项。
  • 如果您想对依赖项中的数据进行存根,以便其所有方法返回您需要的测试值,则监视该依赖项。
于 2018-12-05T00:10:58.927 回答
3

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

spy如果我们想模拟一些方法并且对于剩余的方法必须进行实际调用,则使用。

于 2021-11-30T06:48:12.663 回答