Table of contents
1.
Introduction
2.
Spy
3.
Create Spy
4.
Stub a Spy
5.
Mock vs Spy
6.
Understanding the Mockito NotAMockException
7.
FAQs
8.
Key Takeaways
Last Updated: Mar 27, 2024
Easy

Spying

Author Toohina Barua
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Mockito is a Java framework that allows you to mock and test Java applications. This framework makes use of the Reflection API internally. Mockito makes testing easier by generating mock objects and removing external dependencies. It's used in conjunction with other testing frameworks such as JUnit or TestNG.
We'll learn about spies in Mockito in this article.

Spy

A Spy is similar to a partial mock in that it tracks the interactions with the object. It also allows us to use all of the object's standard methods. When we call a spy object method, the real method is called as well (unless it is stubbed).

Create Spy

Creating Spy Using Mockito.spy() Method:

We've created a spy for an ArrayList in the code below. This is done with the Mockito.spy() method. The verify() method is used to determine whether or not the interaction occurred. The actual method is invoked when the spy's add() method is called, and elements are added to the list. We can confirm this by looking at the length of the list.

public class MockitoSpyDemo
{
@Test
public void test()
{
ArrayList<Integer> arrList = new ArrayList<>();
ArrayList<Integer> spyArrList = Mockito.spy(arrList);


//Adding Elements
spyArrList.add(5);
spyArrList.add(10);
spyArrList.add(15);

        //Verifying interactions
Mockito.verify(spyArrList).add(5);
Mockito.verify(spyArrList).add(10);
Mockito.verify(spyArrList).add(15);

        //Verifying that elements were actually added to the list
assertEquals(3, spyArrList.size());
}
}


Creating Spy using @Spy Annotation:

The @Spy annotation can also be used to create a spy object. We can use the MockitoAnnotations.initMocks(this) method to enable Mockito annotations. Alternatively, we can make use of the built-in runner @RunWith (MockitoJUnitRunner.class). Below are some examples for both of them.

Example code using MockitoAnnotations.initMocks(this):

public class MockitoSpyDemo
{
//Using @Spy Annotation
@Spy
ArrayList<Integer> spyArrList = new ArrayList<>();

@Before
public void init()
{
MockitoAnnotations.initMocks(this); 
}

@Test
public void test()
{
spyArrList.add(5);
spyArrList.add(10);
spyArrList.add(15);

Mockito.verify(spyArrList).add(5);
Mockito.verify(spyArrList).add(10);
Mockito.verify(spyArrList).add(15);

assertEquals(3, spyArrList.size());
}
}


Example code using @RunWith(MockitoJUnitRunner.class):

@RunWith(MockitoJUnitRunner.class)
public class MockitoSpyDemo
{
//Using @Spy Annotation
@Spy
ArrayList<Integer> spyArrList = new ArrayList<>();

@Test
public void test()
{
spyArrList.add(5);
spyArrList.add(10);
spyArrList.add(15);

Mockito.verify(spyArrList).add(5);
Mockito.verify(spyArrList).add(10);
Mockito.verify(spyArrList).add(15);

assertEquals(3, spyArrList.size());
}
}

Stub a Spy

To override a method's normal behaviour, we can stub a Spy. Let's change the default behaviour of ArrayLists' contains() method. For the value 5, let's make this method always return false.

public class MockitoSpyDemo
{
@Test
public void test()
{
ArrayList<Integer> spyArrList = Mockito.spy(new ArrayList<>());
spyArrList.add(5);
Mockito.verify(spyArrList).add(5);
assertEquals(true, spyArrList.contains(5));//Default normal behavior

Mockito.doReturn(false).when(spyArrList).contains(5);
assertEquals(false, spyArrList.contains(5));//Stubbed Behavior
}
}


Let's override ArrayLists' size() method. The list size should always be returned as 5.

public class MockitoSpyDemo
{
@Test
public void test()
{
ArrayList<Integer> spyArrList = Mockito.spy(new ArrayList<>()); 
spyArrList.add(5);
Mockito.verify(spyArrList).add(5);
assertEquals(1, spyArrList.size());//Default Normal Behavior

Mockito.doReturn(5).when(spyArrList).size();
assertEquals(5, spyArrList.size());//Stubbed Behavior
}
}

Mock vs Spy

In Mockito, let's talk about the differences between Mock and Spy. We won't go over the theoretical distinctions between the two concepts; instead, we'll look at how they differ within Mockito.
When Mockito creates a mock, it does so from the Class of a Type rather than an actual instance. The mock simply creates a skeleton instance of the Class that is fully instrumented to track interactions with it.
The spy, on the other hand, wraps an existing instance. It will behave in the same way as the standard instance, with the exception that it will be instrumented to track all interactions with it.

Here's how we'll mock 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 we can see, adding an element to the mocked list has no effect; it simply calls the method with no other consequences.

In contrast, a spy will 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());
}

Understanding the Mockito NotAMockException

We'll learn about the Mockito NotAMockException in the last section. This is one of the most common exceptions we'll come across when misusing mocks or spies.

Let's start with an understanding of the circumstances that can lead to this exception:

List<String> list = new ArrayList<String>();
Mockito.doReturn(100).when(list).size();


assertEquals("Size should be 100: ", 100, list.size());


We'll get the following error if we run this code snippet:

org.mockito.exceptions.misusing.NotAMockException: 


Argument passed to when() is not a mock!

Example of correct stubbing:

 doThrow(new RuntimeException()).when(mock).someMethod();


Thankfully, the Mockito error message makes it crystal clear what the issue is. The list object in our example is not a mock. The when() method of Mockito expects a mock or spy object as an argument.

The Exception message even describes what a correct invocation should look like, as we can see. Now that we have a better understanding of the issue, let's fix it according to the advice:

final List<String> spyList = Mockito.spy(new ArrayList<String>());
Mockito.doReturn(100).when(spyList).size();


assertEquals("Size should be 100: ", 100, spyList.size());


Our example now works as expected, and the Mockito NotAMockException is no longer present.

FAQs

  1. What is a Mock in Mockito?
    When Mockito creates a mock, it does so from the Class of a Type rather than an actual instance. The mock simply creates a skeleton instance of the Class that is fully instrumented to track interactions with it.
  2. What does Spy do?
    An existing instance will be wrapped by the spy. It will behave in the same way as the standard instance, with the exception that it will be instrumented to track all interactions with it.
  3. Which API do we use to spy on a real object?
    The API is Mockito.spy() to spy on a real object.
  4. What argument does a Mockito when() method expect?
    The Mockito when() method expects a mock or spy object as the argument.
  5. When is NotAMockException thrown?
    When we try to use mock-specific methods on another object, we get this exception.

Key Takeaways

In this article, we have extensively discussed the theoretical and practical implementation of Spying in Mockito.
We hope that this blog has helped you enhance your knowledge regarding spies in Mockito and if you would like to learn more, check out our articles on Coding Ninjas Studio. Do upvote our blog to help other ninjas grow. Happy Coding!

Live masterclass