183

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?

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
Abhinav
  • 1,911
  • 2
  • 13
  • 11
  • 2
    possible duplicate of [mockito mock vs. spy](http://stackoverflow.com/questions/15052984/mockito-mock-vs-spy) – rds Mar 31 '15 at 12:11
  • 2
    Possible duplicate of [Mocking vs. Spying in mocking frameworks](http://stackoverflow.com/questions/12827580/mocking-vs-spying-in-mocking-frameworks) – PenguinEngineer Mar 28 '17 at 20:16

9 Answers9

157

Technically speaking both "mocks" and "spies" are a special kind of "test doubles".

Mockito is unfortunately making the distinction weird.

A mock in mockito is a normal mock in other mocking frameworks (allows you to stub invocations; that is, return specific values out of method calls).

A spy in mockito is a partial mock in other mocking frameworks (part of the object will be mocked and part will use real method invocations).

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Crazyjavahacking
  • 9,343
  • 2
  • 31
  • 40
123

Both can be used to mock methods or fields. The difference is that in mock, you are creating a complete mock or fake object while in spy, there is the real object and you just spying or stubbing specific methods of it.

While in spy objects, of course, since it is a real method, when you are not stubbing the method, then it will call the real method behavior. If you want to change and mock the method, then you need to stub it.

Consider the example below as a comparison.

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

When shoud you use mock or spy? If you want to be safe and avoid calling external services and just want to test the logic inside of the unit, then use mock. If you want to call external service and perform calling of real dependency, or simply say, you want to run the program as it is and just stub specific methods, then use spy. So that’s the difference between spy and mock in mockito.

Vu Truong
  • 1,655
  • 1
  • 12
  • 10
  • Good answer but it will throw verify() on a mock only error and will not run the tests unless you initialize your lists in @Before setUp() method just like here mockList = mock(ArrayList.class); spyList = spy(ArrayList.class); and remove the mock and spy annotation suggested here. I have tested it and my tests are passing now. – The_Martian Jul 01 '19 at 19:29
  • @The_Martian It is throwing because MockitoAnnotations.initMocks(this) is not being called in the Before setUp() method. – Andrew Chelix May 10 '21 at 06:46
38

In short:

@Spy and @Mock are used heavily in testing of code, but developers do confuse in cases when to use one of them and thus developers end up in using @Mock to be safe.

  • Use @Mock when you want to just test the functionality externally without actually calling that method.
  • Use @Spy when you want to test the functionality externally + internally with the very method being called.

Below is the example where i have taken the scenario of Election20xx in America.

Voters can be divided according to VotersOfBelow21 and VotersOfABove21.

The ideal Exit poll says that Trump will win the election because VotersOfBelow21 and VotersOfABove21 both will vote for trump saying "We elected President Trump "

But this is not the real scenario:

Voters of both age group voted for Trump because they had No other effective choice other than Mr Trump.

So how do you Test it??

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

}

Now Note in above first two classes, both the age group people says that they do not have a better choice than trump. Which explicitly means that they voted for Trump just because they had no choice.

Now the ElectionOfYear20XX says that Trump Won because both age group voted for him overwhelmingly.

If we were to Test the ElectionOfYear20XX with @Mock, then we might not be able to get the real reason why Trump won, we will be just testing the external reason.

If we test the ElectionOfYear20XX with @Spy, then we get the real reason why Trump won with the external exit poll results, i.e internally + externally.


Our ELectionOfYear20XX_Test class:

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

}

This should output just the logic test results i.e. external check:

We elected President Trump 
We elected President Trump 

Testing with @Spy externally as well as internally with actual method invocation.

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

}

Output:

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 
Vishwa Ratna
  • 5,567
  • 5
  • 33
  • 55
22

TL;DR version,

With mock, it creates a bare-bone shell instance for you.

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

With spy you can partially mock on an existing instance

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

Typical use case for Spy: the class has a parameterized constructor, you want to create the object first.

del bao
  • 1,084
  • 1
  • 11
  • 20
22

mock is used if we want to mock all the methods of a class.

spy is used if we want to mock some methods and for remaining methods actual call has to be made.

ZygD
  • 22,092
  • 39
  • 79
  • 102
Hamdhan Azeez T
  • 429
  • 4
  • 9
19

I have created a runable example here https://www.surasint.com/mockito-with-spy/

I copy some of it here.

If you have something like this code:

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

You may don't need spy because you can just mock DepositMoneyService and WithdrawMoneyService.

But with some legacy code, dependency is in the code like this:

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

Yes, you can change to the first code but then API is changed. If this method is being used by many places, you have to change all of them.

Alternative is that you can extract the dependency out like this:

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

Then you can use the spy the inject the dependency like this:

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

More detail in the link above.

Ali Ben Messaoud
  • 11,690
  • 8
  • 54
  • 87
Surasin Tancharoen
  • 5,520
  • 4
  • 32
  • 40
17

The best place to start is probably the docs for mockito.

On a general note the mockito mock allows you to create stubs.

You would create a stub method if, for example, that method does an expensive operation. Say, it gets a database connection, retrieves a value from the database and returns it to the caller. Getting the db connection might take 30 seconds, slowing your test execution to the point where you'll likely context switch (or stop running the test).

If the logic you are testing doesn't care about the database connection then you could replace that method with a stub which returns a hard coded value.

The mockito spy lets you check whether a method calls other methods. This can be very useful when trying to get legacy code under test.

It is usful if you are testing a method that works through side effects, then you would use a mockito spy. This delegates calls to the real object and allows you to verify method invocation, number of times invoked etc.

Jaimie Whiteside
  • 1,180
  • 9
  • 21
12

I like the simplicity of this recommendation:

  • If you want to be safe and avoid calling external services and just want to test the logic inside of the unit, then use mock.
  • If you want to call external service and perform calling of real dependencies, or simply say, you want to run the program as it is and just stub specific methods, then use spy.

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

A common difference is:

  • If you want to directly stub the method(s) of a dependency, then Mock that dependency.
  • If you want to stub the data in a dependency so that all its methods return testing values that you need, then Spy that dependency.
leo9r
  • 2,037
  • 26
  • 30
  • 1
    Note that Spy and Mock are always meant for dependencies, and not for the system under test. – leo9r Mar 11 '19 at 23:26
8

A bit late to the show, but I feel the other responses don't do a very good job at illustrating the differences between Spies and Mocks. So here is a small demo

Given a service to test

public class EmailService {

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

We can now do some testing with four different scenarios.

  1. Calling and stubbing a mock
  2. Calling a mock without stubbing
  3. Calling a spy without stubbing
  4. Calling and stubbing a spy

Setup:

private final String testEmail = "randomuser@domain.com";
private final String success = "SUCCESS";
@Mock EmailService emailService;
@Spy EmailService emailServiceSpy;

Tests:

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

A Mock if not stubbed will return a null value, whereas a Spy if not stubbed will call the implemented method inside of the concrete class.

Ramesh R
  • 7,009
  • 4
  • 25
  • 38
James Wagstaff
  • 150
  • 1
  • 7