When and how to spy objects in Mockito

When and how to spy objects in Mockito

When it comes to mocking objects in Java unit testing, Mockito is the de-facto framework. It is powerful, easy to use, and supports different testing approaches, whether TDD, BDD, etc. In addition to that Mockito supports spies. In this tutorial, we discuss when and how to spy objects in Mockito in the correct way.

What’s a spy object

Before we discuss how to spy objects using Mockito, we need to know what is a spy object and how spying differs from mocking.

A mock object is a dummy object created from a class Skeleton. Calling a mocked method does not do anything if it is not stubbed.

On the other hand, a spy object is a real object and behaves like one. It means calling a spy method invokes the actual method unless it is stubbed. The benefit of a spy object compare to a real object is the ability to stub methods and verify the calls if needed.

Spies shouldn’t be used lightly. It should be avoided as much as possible since it is often the indicator of code untestability. However, there are valid use cases to use the Mockito spies. Some are as follows,

  • Dealing with legacy codes
  • Interested in implementing only a method or two of a large class (HttpServletRequest)
  • Dealing with nested mocked objects

How to spy a class

To spy a class, annotate the class with @Spy annotation. Then if you don’t stub the methods, the actual class code will execute. Let’s understand it better by looking at the below example.

public class Calculator {

    public int multiply(int a, int b) {
        int result = a;
        for (int i = 1; i < b; i++) {
            result = add(a, result);
        }
        return result;
    }

    public int divide(int a, int b) {
        int result = a;
        for (int i = 1; i < b; i++) {
            result = subtract(a, result);
        }
        return result;
    }

    public int add(int a, int b) {
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }
}

We have a simple Calculator class that supports four operations (add, subtract, multiply, and divide). Division and multiplication are built using the add and the subtract methods.

Now assume that we want to test the calculator class. We can write as follows:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
public class TestCalculator {

    private Calculator calculator = new Calculator();

    @Test
    void testAdd() {
        int a = 10;
        int b = 20;

        int result = calculator.add(a, b);
        assertEquals(30, result);

    }

    @Test
    void testMultiply() {
        int a = 2;
        int b = 4;

        int result = calculator.multiply(a, b);
        assertEquals(8, result);
    }
}

So far so good. The tests are green, and everything looks good. But what if we want to test the internal and verify the number of calls to the add method when we call to multiply? Yes, that’s an anti-pattern. We should never care about the internals. However, sometimes, we have to, especially when dealing with fragile legacy code.

Back to our example. To test the number of calls to the add method, we can spy our calculator class as follows.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
public class TestCalculator {

    @Spy // Spy the calculator class
    private Calculator calculator;

    @Test
    void testAdd() {
        int a = 10;
        int b = 20;

        int result = calculator.add(a, b);
        assertEquals(30, result);

    }

    @Test
    void testMultiply() {
        int a = 2;
        int b = 4;

        int result = calculator.multiply(a, b);
        verify(calculator, times(3)).add(anyInt(), anyInt()); // verify the number of calls
        assertEquals(8, result);
    }
}

Conclusion

In this article, we covered when and how to spy objects in Mockito. By contrast to mock objects, spies are real class instantiations. As a result, calling a spy method invokes the actual class method. However, spies provide the flexibility to stub their methods selectively. It is important to note that one should refrain from using spy objects as much as possible.

References

Inline/featured images credits