Mockito @Spy Annotation

November 16, 2020 No comments Mockito Java Framework Spy Testing

1. Introduction

Mockito is a framework created for mocking objects for unit tests in mind. In this article, we are going to present how to use Mockito @Spy annotation to create wrapped objects whose behavior is tracked and could be verified in tests.

2. Initialize Mockito annotations

To activate Mockito annotations we need to use one of three given solutions:

  • add @RunWith(MockitoJUnitRunner.class) at the top of unit test class,
  • call MockitoAnnotations.initMocks(this) method in the @Before method of unit test class,
  • use MockitoJUnit.rule() to create MockitoRule class.

3. Using spies in Mockito

@Spy annotation is used to wrap real objects and add a tracking mechanism for its method calls and value initializations. Spied instances work exactly in the same way as original ones unless specified otherwise. We can always configure the behavior of a spied object method using the same when(...).then(...) syntax we would use with mock instances.

Let's check a simple example of a JUnit test with a spied object:

package com.frontbackend.libraries.mockito;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class MockitoSpyTest {

    @Spy
    private final List<String> list = new ArrayList<>();

    @Test
    public void shouldAddItemsToListSuccessfully() {
        list.add("one");
        list.add("two");

        verify(list, times(2)).add(anyString());

        verify(list).add("one");
        verify(list).add("two");

        Assert.assertEquals(2, list.size());
    }
}

In this example:

  • @Spy private final List<String> list = new ArrayList<>(); - defined a spied instance of List type,
  • list.add("one"); list.add("two"); - we are making some operations on the spied object (every call is tracked by Mockito),
  • verify(list, times(2)).add(anyString()); - verify() method on the mock object is used here to verify that the specified conditions are met,
  • verify(list).add("one"); - we are checking if method add was called with "one" String as a parameter,
  • verify(list).add("two"); - we are checking if method add was called with "two" String as an input parameter,
  • Assert.assertEquals(2, list.size()); - this assertion proves that add method was called on the "real" instance.

The same example but using Mockito.spy(...) method could be as follows:

package com.frontbackend.libraries.mockito;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class MockitoSpyTest {

    @Test
    public void shouldAddItemsToListSuccessfullyUsingMockMethod() {
        List<String> list = Mockito.spy(new ArrayList<>());

        list.add("one");
        list.add("two");

        verify(list, times(2)).add(anyString());

        verify(list).add("one");
        verify(list).add("two");

        Assert.assertEquals(2, list.size());
    }
}

4. Stubbing a Spy

We can configure spied objects in such a way that the selected methods return a specific value this operation is called stubbing.

Let's check an example of a stubbed object in the test:

package com.frontbackend.libraries.mockito;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class MockitoSpyTest {

    @Spy
    private final List<String> list = new ArrayList<>();

    @Test
    public void shouldReturnDifferentSizeWhenStubbed() {
        when(list.size()).thenReturn(100);

        list.add("one");
        list.add("two");

        verify(list, times(2)).add(anyString());

        verify(list).add("one");
        verify(list).add("two");

        Assert.assertEquals(100, list.size());
    }
}

In this example:

  • when(list.size()).thenReturn(100); - we are overrided original size() method on List type, and configure it to return 100 instead of the real list size,
  • Assert.assertEquals(100, list.size()); - assertion on list.size() now compare the size with 100.

5. Difference between @Mock and @Spy in Mockito

Mock objects replace completely mocked class. The mocked object will be returning configured or default values. We can create a mock out of the interface. Mocked objects do not have to be an instance.

Spying is when we take an existing instance and override only some or none methods. This type is useful when we want to mock just a few methods from a huge class. When you use the spy then the real methods are called (unless a method was stubbed).

Note that Mockito warns that partial mocking isn't a good practice. Spy (or partial mocking) is recommended to test legacy code.

Let's check a simple example with a mocked object:

@Mock
private List<String> mockedList;

@Test
public void testMockedList() {
    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

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

As you can see added a single value to the mocked list doesn't change it at all. The size of the list is still 0.

Now let's see this example with spied instance:

@Spy
private List<String> spiedList = new ArrayList<>();

@Test
public void testSpiedList() {
    spiedList.add("one");
    Mockito.verify(spiedList).add("one");

    Assert.assertEquals(1, spiedList.size());
}

When we called the add(...) method on the spied object and this method was not configured with when(...).then(...) syntax, the real implementation will be called. As a result, the size of the list will be 1.

6. Conclusion

In this article, we presented Mockito @Spy annotation. We show several examples of how to use it in JUnit tests and what is the difference between spied and mocked objects.

All code snippets used in this article, are available under the GitHub repository.

{{ message }}

{{ 'Comments are closed.' | trans }}