Book Image

Mockito Essentials

By : Sujoy Acharya
Book Image

Mockito Essentials

By: Sujoy Acharya

Overview of this book

Table of Contents (14 chapters)

Exploring a test spy


A spy secretly obtains the information of a rival or someone very important. As the name suggests, a spy object spies on a real object. A spy is a variation of a stub, but instead of only setting the expectation, a spy records the method calls made to the collaborator. A spy can act as an indirect output of the unit under test and can also act as an audit log.

We'll create a spy object and examine its behavior; the following are the steps to create a spy object:

  1. Launch Eclipse, open <work_space>, and go to the 3605OS_TestDoubles project.

  2. Create a com.packt.testdoubles.spy package and create a StudentService class. This class will act as a course register service. The following is the code for the StudentService class:

    public class StudentService {
    
      private Map<String, List<Student>> studentCouseMap = new HashMap<>();
    
      public void enrollToCourse(String courseName,Student student){
        List<Student> list = studentCouseMap.get(courseName);
        if (list == null) {
          list = new ArrayList<>();
        }
    
        if (!list.contains(student)) {
          list.add(student);
        }
        studentCouseMap.put(courseName, list);
      }
    
    }

    The StudentService class contains a map of the course names and students. The enrollToCourse method looks up the map; if no student is enrolled, then it creates a collection of students, adds the student to the collection, and puts the collection back in the map. If a student has previously enrolled for the course, then the map already contains a Student collection. So, it just adds the new student to the collection.students list.

  3. The enrollToCourse method is a void method and doesn't return a response. To verify that the enrollToCourse method was invoked with a specific set of parameters, we can create a spy object. The service will write to the spy log, and the spy will act as an indirect output for verification. Create a spy object to register method invocations. The following code gives the method invocation details:

    class MethodInvocation {
    
      private List<Object> params = new ArrayList<>();
      private Object returnedValue = null;
      private String method;
    
      public List<Object> getParams() {
        return params;
      }
    
      public MethodInvocation addParam(Object parm){
        getParams().add(parm);
        return this;
      }
    
      public Object getReturnedValue() {
        return returnedValue;
      }
    
      public MethodInvocation setReturnedValue(Object returnedValue) {
        this.returnedValue = returnedValue;
        return this;
      }
    
      public String getMethod() {
        return method;
      }
    
      public MethodInvocation setMethod(String method) {
       this.method = method;
        return this;
      }
    }

    The MethodInvocation class represents a method invocation: the method name, a parameter list, and a return value. Suppose a sum() method is invoked with two numbers and the method returns the sum of two numbers, then the MethodInvocation class will contain a method name as sum, a parameter list that will include the two numbers, and a return value that will contain the sum of the two numbers.

    Note

    Note that the setter methods return this(MethodInvocation). This coding approach is known as builder pattern. It helps to build an object in multiple steps. Java StringBuilder is an example of such a use:

    StringBuilder builder = new StringBuilder();
    builder.append("step1").append("step2")…

    The following is the spy object snippet. It has a registerCall method to log a method call instance. It has a map of strings and a List<MethodInvocation> method. If a method is invoked 10 times, then the map will contain the method name and a list of 10 MethodInvocation objects. The spy object provides an invocation method that accepts a method name and returns the method invocation count from the invocationMap class:

    public class StudentServiceSpy {
      private Map<String, List<MethodInvocation>> invocationMap = new HashMap<>();
    
      void registerCall(MethodInvocation invocation) {
        List<MethodInvocation> list = invocationMap.get(invocation.getMethod());
        if (list == null) {
          list = new ArrayList<>();
        }
        if (!list.contains(invocation)) {
          list.add(invocation);
        }
    
        invocationMap.put(invocation.getMethod(), list);
      }
    
      public int invocation(String methodName){
        List<MethodInvocation> list = invocationMap.get(methodName);
        if(list == null){
          return 0;
        }
    
        return list.size();
      }
    
      public MethodInvocation arguments(String methodName, int invocationIndex){
        List<MethodInvocation> list = invocationMap.get(methodName);
        if(list == null || (invocationIndex > list.size())){
          return null;
        }
        return list.get(invocationIndex-1);
      }
    }

    The registerCall method takes a MethodInvocation object and puts it in a map.

  4. Modify the StudentService class to set a spy and log every method invocation to the spy object:

      private StudentServiceSpy spy;
      public void setSpy(StudentServiceSpy spy) {
        this.spy = spy;
      }
      public void enrollToCourse(String courseName, Student student) {
        MethodInvocation invocation = new MethodInvocation();
        invocation.addParam(courseName).addParam(student).setMethod("enrollToCourse");
        spy.registerCall(invocation);
    
        List<Student> list = studentCouseMap.get(courseName);
        if (list == null) {
          list = new ArrayList<>();
        }
        if (!list.contains(student)) {
          list.add(student);
        }
    
        studentCouseMap.put(courseName, list);
      }
  5. Write a test to examine the method invocation and arguments. The following JUnit test uses the spy object and verifies the method invocation:

    public class StudentServiceTest {
      StudentService service = new StudentService();
      StudentServiceSpy spy = new StudentServiceSpy();
    
      @Test
      public void enrolls_students() throws Exception {
        //create student objects
        Student bob = new Student("001", "Robert Anthony");
        Student roy = new Student("002", "Roy Noon");
        //set spy
        service.setSpy(spy);
    
        //enroll Bob and Roy
        service.enrollToCourse("english", bob);
        service.enrollToCourse("history", roy);
        //assert that the method was invoked twice
        assertEquals(2, spy.invocation("enrollToCourse"));
    
        //get the method arguments for the first call
        List<Object> methodArguments = spy.arguments
    ("enrollToCourse", 1).getParams();
    
        //get the method arguments for the 2nd call
        List<Object> methodArguments2 = spy.arguments
    ("enrollToCourse", 2).getParams();
    
        //verify that Bob was enrolled to English first
        assertEquals("english", methodArguments.get(0));
        assertEquals(bob, methodArguments.get(1));
    
        //verify that Roy was enrolled to history
        assertEquals("history", methodArguments2.get(0));
       assertEquals(roy, methodArguments2.get(1));
    
      }
    
    }