Mockito Spy (@Spy)

A spy,unlike a mock is used to monitor the actual call to the functioning of a method under test.When a mocked instance’s method is invoked,it does nothing and we can control its returned result or even throw exceptions.With a spied object,the actual method is called .We can however mock its functionality the same way we do with a mock.

Lets see @Spy with an example.

Spy Example

We shall write a test for the TeacherService class, createTeacher() method.

The TeacherService source code is as below.

 

package com.school.relationships.services;

import com.school.relationships.entities.Teacher;

import com.school.relationships.models.TeacherModel;

import com.school.relationships.repositories.TeacherRepository;

import com.school.relationships.util.FormattingUtils;

import java.util.Optional;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service

@Slf4j

public class TeacherService {

   

    @Autowired

    private TeacherRepository teacherRepository;

    @Autowired

    private FormattingUtils formattingUtils;

   

    public Teacher createTeacher(TeacherModel model) {

        try {

            Teacher teacher = new Teacher();

            teacher.setFullName(model.getTeacherName());

            teacher.setTeacherNo(formattingUtils.formatTeacherNumber(model.getTeacherNumber()));

            log.info("Created teacher name={},teacher no={}", teacher.getFullName(), teacher.getTeacherNo());

            return teacherRepository.save(teacher);

        } catch (Exception e) {

            log.error("Error creating teacher > ", e);

            throw e;

        }

    }

   

    public Optional<Teacher> findTeacherById(Integer idteacher) {

        return teacherRepository.findById(idteacher);

    }

   

    public Optional<Teacher> findTeacherAndSubjectsTaught(Integer idteacher) {

        return teacherRepository.findTeacherAndSubjectsTaught(idteacher);

    }

}

 

The class has two (2) dependencies TeacherRepository and FormattingUtils.

We are going to mock TeacherRepository’s save() method which persists our Teacher entities and Spy on FormattingUtils which is a utility class for sanitizing and formatting strings on some fields on entities before persisting them in the database.For our TeacherService method,we use the FormattingUtils’s formatTeacherNumber() method to make our passed in teacher number start with ‘TR-‘ and also capitalize the whole string.

By Spying on FormattingUtils ,we can see that an actual call to its  formatTeacherNumber is made by observing the formatted teacher number on the logs .

Our unit test case is as below

 

package com.school.relationships;

import com.school.relationships.entities.Teacher;

import com.school.relationships.models.TeacherModel;

import com.school.relationships.repositories.TeacherRepository;

import com.school.relationships.services.TeacherService;

import com.school.relationships.util.FormattingUtils;

import lombok.extern.slf4j.Slf4j;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.mockito.InjectMocks;

import org.mockito.Mock;

import org.mockito.Mockito;

import org.mockito.Spy;

import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)

@Slf4j

public class TeacherServiceTests {

 

    @Mock

    private TeacherRepository teacherRepository;

    @Spy

    private FormattingUtils formattingUtils;

    @InjectMocks

    private TeacherService teacherService;

 

    @Test

    public void testCreateTeacherSuccessful() {

        log.info("Started testing method createTeacherSuccessful");

        Mockito.when(teacherRepository.save(Mockito.any(Teacher.class))).thenReturn(Mockito.mock(Teacher.class));

        TeacherModel model = new TeacherModel();

        model.setTeacherName("Mr. John");

        model.setTeacherNumber("123");

        teacherService.createTeacher(model);

        log.info("Finished testing method createTeacherSuccessful");

    }

}

 

Once we build our project to run the unit tests,

We see below log line ‘20:40:44.609 [main] INFO com.school.relationships.services.TeacherService - Created teacher name=Mr. John,teacher no=TR-123’

 

 

We can see that an actual call to the formatTeacherNumber(String trNo) method in FormattingUtils class was called.

Stubbing a spy

We can also stub a spy by modifying its execution to return a different result.

Let’s do this by writing the below test case.

 

@Test

    public void testCreateTeacherSuccessfulWithFormattingUtilsModification() {

 

        log.info("Started testing method testCreateTeacherSuccessfulWithFormattingUtilsModification");

        Mockito.when(teacherRepository.save(Mockito.any(Teacher.class))).thenReturn(Mockito.mock(Teacher.class));

        Mockito.when(formattingUtils.formatTeacherNumber(Mockito.anyString())).thenAnswer((InvocationOnMock iom) -> {

            String trNo = iom.getArgument(0, String.class);

            return new StringBuilder().append("tr-").append(trNo.toLowerCase()).toString();

        });

        TeacherModel model = new TeacherModel();

        model.setTeacherName("Mr. John");

        model.setTeacherNumber("123");

        teacherService.createTeacher(model);

        log.info("Finished testing method testCreateTeacherSuccessfulWithFormattingUtilsModification");

    }

 

We override the functionality of the formatTeacherNumber method so that we append lower case “tr-” and the lowercase teacher number together vs the original version in which the result was in uppercase.

On building and running the project,we get below log extract for the testcase.

 

 

The log line tagged latest is from the test case with stubbed spy while  the one tagged original is from the test case with spy ,i.e. the original spy method called.

Tip on scenarios to use spy

When writing tests, not all functionality needs to be mocked. For some scenarios, we actually want to see the actual code executing .This can be helpful so that the returned result can be used in writing meaningful assertions.In this situtations we use Spy .

The code for this project at this stage is available at Github.

About the Author - John Kyalo Mbindyo(Bsc Computer Science) is a Senior Application Developer currently working at NCBA Bank Group,Nairobi- Kenya.He is passionate about making programming tutorials and sharing his knowledge with other software engineers across the globe. You can learn more about him and follow him on  Github.