What is the difference between mocking and spying when using Mockito?

128,721

Solution 1

The answer is in the documentation:

Real partial mocks (Since 1.8.0)

Finally, after many internal debates & discussions on the mailing list, partial mock support was added to Mockito. Previously we considered partial mocks as code smells. However, we found a legitimate use case for partial mocks.

Before release 1.8 spy() was not producing real partial mocks and it was confusing for some users. Read more about spying: here or in javadoc for spy(Object) method.

callRealMethod() was introduced after spy(), but spy() was left there of course, to ensure backward compatibility.

Otherwise, you're right: all the methods of a spy are real unless stubbed. All the methods of a mock are stubbed unless callRealMethod() is called. In general, I would prefer using callRealMethod(), because it doesn't force me to use the doXxx().when() idiom instead of the traditional when().thenXxx()

Solution 2

Difference between a Spy and a Mock

When Mockito creates a mock – it does so from the Class of a Type, not from an actual instance. The mock simply creates a bare-bones shell instance of the Class, entirely instrumented to track interactions with it. On the other hand, the spy will wrap an existing instance. It will still behave in the same way as the normal instance – the only difference is that it will also be instrumented to track all the interactions with it.

In the following example – we create a mock of the ArrayList class:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

As you can see – adding an element into the mocked list doesn’t actually add anything – it just calls the method with no other side-effect. A spy on the other hand will behave differently – it will actually call the real implementation of the add method and add the element to the underlying list:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

Here we can surely say that the real internal method of the object was called because when you call the size() method you get the size as 1, but this size() method isn’t been mocked! So where does 1 come from? The internal real size() method is called as size() isn’t mocked (or stubbed) and hence we can say that the entry was added to the real object.

Source: http://www.baeldung.com/mockito-spy + self notes.

Solution 3

If there is an object with 8 methods and you have a test where you want to call 7 real methods and stub one method you have two options:

  1. Using a mock you would have to set it up by invoking 7 callRealMethod and stub one method
  2. Using a spy you have to set it up by stubbing one method

The official documentation on doCallRealMethod recommends using a spy for partial mocks.

See also javadoc spy(Object) to find out more about partial mocks. Mockito.spy() is a recommended way of creating partial mocks. The reason is it guarantees real methods are called against correctly constructed object because you're responsible for constructing the object passed to spy() method.

Solution 4

Spy can be useful when you want to create unit tests for legacy code.

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.

Solution 5

[Test double types]

Mock vs Spy

Mock is a bare double object. This object has the same methods signatures but realisation is empty and return default value - 0 and null

Spy is a cloned double object. New object is cloned based on a real object but you have a possibility to mock it

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();
}
Share:
128,721

Related videos on Youtube

Victor Grazi
Author by

Victor Grazi

I am a Java solution architect/application designer/Java Champion. My first love is Java concurrency. My open-source project Java Concurrent Animated is a popular self-executing jar. Please download it at http://sourceforge.net/projects/javaconcurrenta

Updated on October 11, 2020

Comments

  • Victor Grazi
    Victor Grazi over 3 years

    What would be a use case for a use of a Mockito spy?

    It seems to me that every spy use case can be handled with a mock, using callRealMethod.

    One difference I can see is if you want most method calls to be real, it saves some lines of code to use a mock vs. a spy. Is that it or am I missing the bigger picture?

  • Eyal Roth
    Eyal Roth almost 10 years
    The problem with preferring mock over spy in these cases, is when the class uses a member which isn't injected into it (but locally initialized), and is later used by the "real" method; in the mock, the member will be initialized to its default Java value, which might cause wrong behavior or even a NullPointerException. The way to pass this is to add an "init" method and then "really" calling it, but that seems a bit of an overdo to me.
  • black
    black over 7 years
    Don't you mean size() returns 1?
  • mike
    mike over 7 years
    In the first example, why is mockedList.size() returning 0 if that method hasn't been stubbed out either? Is that just a default value given the return type of the method?
  • gdbj
    gdbj about 7 years
    From the doc: "spies should be used carefully and occasionally, for example when dealing with legacy code." The unit testing space suffers from too many ways of doing the same thing.
  • realPK
    realPK almost 7 years
    @mike: mockedList.size() returns an int and default value of int is 0 in Java. If you try executing assertEquals(0, mockedList.size());after mockedList.clear();, result stays same.
  • PesaThe
    PesaThe almost 4 years
    This answer is well and simply written and helped me to finally understand the difference between mock and spy. Nice one.