Sunday 25 June 2017

Mockito Example Stubbing Void Methods

In this post we will learn about using TestNG and Mockito together. We will also learn about stubbing void methods with Mockito.
Mockito library enables mocks creation, verification and stubbing. In simple terms, mock objects comes handy in situations like when you are testing a class [A] which depends on another class [B]. To test A in isolation, you can mock B so that while creating real instance of A, mock object of B will be injected into A instance. In this manner, you can test A in isolation without worrying about how B should be initialized & implemented.Let’s begin.

Include Mockito as dependency
To use mockito, make sure to include mockito dependency in your project pom.xml, as shown below
  <dependency>
   <groupId>org.mockito</groupId>
   <artifactId>mockito-all</artifactId>
   <version>1.10.19</version>
   <scope>test</scope>
  </dependency>
Now, Let’s take a traditional example of Service and data layers in a web application. We will be testing service layer while mocking data layer.
Service Interface :
package com.websystique.testng;

import java.util.List;

public interface EmployeeService {

 void saveEmployee(Employee employee);

 List<Employee> findAllEmployees();

 void deleteEmployeeBySsn(String ssn);
}
DAO Interface :
package com.websystique.testng;

import java.util.List;

public interface EmployeeDao {

 void saveEmployee(Employee employee);

 List<Employee> findAllEmployees();

 void deleteEmployeeBySsn(String ssn);
}
Service Implementation :
We will be testing this service. Below Service implementation class depends on DAO to actually perform any action completely. Notice that for demonstration purpose here, i am keeping it simple, No spring (you can, it makes no difference while testing), passing dao dependency directly in constructor.
package com.websystique.testng;

import java.util.List;

public class EmployeeServiceImpl implements EmployeeService{
  
    private EmployeeDao dao;
    
    public EmployeeServiceImpl(EmployeeDao dao){
     this.dao = dao;
    }
     
    public void saveEmployee(Employee employee) {
        dao.saveEmployee(employee);
    }
 
    public List<Employee> findAllEmployees() {
        return dao.findAllEmployees();
    }
 
    public void deleteEmployeeBySsn(String ssn) {
        dao.deleteEmployeeBySsn(ssn);
    }
 
}
DAO Implementation:
Below DAO implementation class provide methods to actually play with database. In this example, again, to keep thing simple, i am not using database/hibernate, but simple dumb methods. You could even leave all theses dao methods empty.
Note that since we will be mocking this dao in service, logic included in the dao methods shown below becomes irrelevant and will not executed anyway. What important here is the method signatures because they will be used while mocking and stubbing. Detailed description of these terms is given further in this post.
package com.websystique.testng;

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

public class EmployeeDaoImpl implements EmployeeDao {

 public void saveEmployee(Employee employee) {
  List<Employee> employees = getEmployeeList();
  for (Employee e : employees) {
   if (e.getName().equals(employee.getName())) {
    throw new RuntimeException("Employee already exist");
   }
  }
  System.out.print("Saving Employee..... saved employee");
 }

 public List<Employee> findAllEmployees() {
  return getEmployeeList();
 }

 public void deleteEmployeeBySsn(String ssn) {
  boolean found = false;
  List<Employee> employees = getEmployeeList();
  for (Employee e : employees) {
   if (e.getSsn().equals(ssn)) {
    found = true;
    System.out.print("Deleting employee by SSN..... deleted");
    return;
   }
  }

  if (!found) {
   throw new RuntimeException("Employee not found with this SSN");
  }

 }

 public List<Employee> getEmployeeList() {
  List<Employee> employees = new ArrayList<Employee>();
  Employee e1 = new Employee();
  e1.setId(1);
  e1.setName("Axel");

  Employee e2 = new Employee();
  e1.setId(2);
  e2.setName("Jeremy");

  employees.add(e1);
  employees.add(e2);
  return employees;
 }

}

Model class :
package com.websystique.testng;

import java.math.BigDecimal;

import org.joda.time.LocalDate;

public class Employee {

 private int id;

 private String name;

 private LocalDate joiningDate;

 private BigDecimal salary;

 private String ssn;

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public LocalDate getJoiningDate() {
  return joiningDate;
 }

 public void setJoiningDate(LocalDate joiningDate) {
  this.joiningDate = joiningDate;
 }

 public BigDecimal getSalary() {
  return salary;
 }

 public void setSalary(BigDecimal salary) {
  this.salary = salary;
 }

 public String getSsn() {
  return ssn;
 }

 public void setSsn(String ssn) {
  this.ssn = ssn;
 }

 @Override
 public String toString() {
  return "Employee [id=" + id + ", name=" + name + ", joiningDate="
    + joiningDate + ", salary=" + salary + ", ssn=" + ssn + "]";
 }

}
And Finally…Test class itself :
Note that in this example, we are testing only the service part, not the dao part.
package com.websystique.testng;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
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.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class EmployeeServiceImplTest {

 @Mock
 EmployeeDao dao;

 @InjectMocks
 EmployeeServiceImpl employeeService;

 @Spy
 List<Employee> employees = new ArrayList<Employee>();

 @Captor
 ArgumentCaptor<Employee> captor;

 @BeforeClass
 public void setUp() {
  MockitoAnnotations.initMocks(this);
  employees = getEmployeeList();
 }

 /*
  * Scenario for Successful [error-free] data persistence
  * Void method stubbing example
  */
 @Test
 public void saveEmployee() {
  /*
   * Instruct mockito to do nothing when dao.saveEmployee will be called.
   */
  doNothing().when(dao).saveEmployee(any(Employee.class));
  employeeService.saveEmployee(employees.get(0));
  
  /*
   * Verify that dao.saveEmployee was indeed called one time. 
   */
  verify(dao, times(1)).saveEmployee(captor.capture());

  /*
   * Assert that dao.saveEmployee was called with a particular Employee, assert employee details 
   */
  Assert.assertEquals(captor.getValue().getName(), "Axel");
  
  Assert.assertEquals(2, employees.size());
  verify(employees, times(2)).add(any(Employee.class));
 }

 /*
  * Scenario for Failed data persistence (due to existing user)
  * Void method stubbing example
  */
 @Test(expectedExceptions = RuntimeException.class)
 public void saveExistingEmployee() {
  /*
   * Instruct mockito to throw an exception when dao.saveEmployee will be called.
   */
  doThrow(RuntimeException.class).when(dao).saveEmployee(employees.get(0));
  employeeService.saveEmployee(any(Employee.class));
 }

 /*
  * Scenario for Successful data retrieval.
  */
 @Test
 public void findAllEmployees() {
  /*
   * Instruct mockito to return pre-populated employees list whenever dao.findAllEmployees will be called.
   */
  when(dao.findAllEmployees()).thenReturn(employees);
  Assert.assertEquals(employeeService.findAllEmployees(), employees);
  verify(dao, times(1)).findAllEmployees();
 }

 /*
  * Scenario for Successful data deletion
  * Void method stubbing example
  */
 @Test
 public void deleteEmployeeBySsn() {
  /*
   * Instruct mockito to do nothing when dao.deleteEmployeeBySsn will be called.
   */
  doNothing().when(dao).deleteEmployeeBySsn(anyString());
  employeeService.deleteEmployeeBySsn(anyString());
  verify(dao, times(1)).deleteEmployeeBySsn(anyString());
 }

 /*
  * Scenario for Failed data deletion (due to no employee found with given ssn)
  * Void method stubbing example
  */
 @Test(expectedExceptions = RuntimeException.class)
 public void deleteEmployeeBySsnNotExist() {
  /*
   * Instruct mockito to throw an exception when dao.deleteEmployeeBySsn will be called.
   * 
   */
  doThrow(RuntimeException.class).when(dao).deleteEmployeeBySsn(anyString());
  employeeService.deleteEmployeeBySsn("XXXX");
  verify(dao, atLeastOnce()).deleteEmployeeBySsn(anyString());
 }

 /*
  * Same as above test case, demonstrates 'doAnswer().when' pattern
  * Void method stubbing example
  */
 @Test(expectedExceptions = RuntimeException.class)
 public void deleteEmployeeBySsnNotExistAgain() {
  /*
   * Alternate way to Instruct mockito to throw an exception when dao.deleteEmployeeBySsn will be called.
   */
  doAnswer(new Answer<Object>() {
   public Object answer(InvocationOnMock invocation) {
    Object[] args = invocation.getArguments();
    String arg = (String) args[0];
    if (arg.equals("UNKNOWN_SSN")) {
     throw new RuntimeException("Item not present");
    }
    return null;
   }
  }).when(dao).deleteEmployeeBySsn(anyString());
  employeeService.deleteEmployeeBySsn("UNKNOWN_SSN");
  verify(dao, atLeastOnce()).deleteEmployeeBySsn(anyString());
 }

 /*
  * Simple data provider method
  */
 public List<Employee> getEmployeeList() {
  Employee e1 = new Employee();
  e1.setId(1);
  e1.setName("Axel");
  e1.setSsn("11111");

  Employee e2 = new Employee();
  e1.setId(2);
  e2.setName("Jeremy");
  e2.setSsn("11112");

  employees.add(e1);
  employees.add(e2);
  return employees;
 }

}

PASSED: deleteEmployeeBySsn
PASSED: deleteEmployeeBySsnNotExist
PASSED: deleteEmployeeBySsnNotExistAgain
PASSED: findAllEmployees
PASSED: saveEmployee
PASSED: saveExistingEmployee

===============================================
    Default test
    Tests run: 6, Failures: 0, Skips: 0
===============================================

Now let’s understand the annotations and features used in above test class:
@Mock: Objects annotated with @Mock are not real instances, they are test objects created by Mockito using Class of the object it is applied on, instrumented to track interactions with them. Once created, mock object will remember all interactions with it. Then you can selectively verify whatever interaction you are interested in.
==> In our example, EmployeeServiceImpl needs EmployeeDao to perform it’s core job. While we are testing the real employeeService, we are mocking the employeeDao. This Mock object will remember all interactions/operation applied on it, which we will verify further down in our test.
@InjectMocks: This annotation is used to mark the field on which injection should be performed. Mockito will create a real instance of the object this annotation is applied on, using either of constructor injection, setter injection, or property injection. Mockito will also make sure to inject any dependencies(@mock objects) the actual object in test might need.
==> In our example, a real instance of EmployeeServiceImpl will be created and it’s dependencies(EmployeeDao e.g.) will be injected using @mock object defined in this class.
@Spy: Objects annotated with @Spy are real instances. These objects are real objects but with a difference that they remember all interactions/operation applied on them, which we can verify further down in our test.
==> In our example, we are using a List object employees (annotated with @Spy) which remembers it’s content and the operation executed on it.
@Captor : @Captor is used to capture argument values which can be asserted further in test, after the actual verification.
==> In our example, we are using @Captor to fetch the employee object which was passed in saveEmployee method. Then we asseted different properties of that captured employee object.
MockitoAnnotations.initMocks: It initializes objects annotated with Mockito annotations like @org.mockito.Mock, @InjectMocks, @Spy & @Captor. It is a mandatory call, otherwise the objects annotated with above mentioned annotated will not be initialized and so your verifications & assertions will fail.
Stubbing : Using stubbing , we can specify a predefined behavior to be performed during our test.
Let’s consider stubbing in context of above tests:
1) saveEmployee() : This is an example of void method stubbing. In this test case, we are defining a behavior and the condition when it will be performed.
doNothing().when(dao).saveEmployee(any(Employee.class));
Here we are instructing to mockito that when dao.saveEmployee(Employee) will be called, nothing should be done. This case is a simulation of successful data persist operation.
2) saveExistingEmployee() : This is an example of void method stubbing.
doThrow(RuntimeException.class).when(dao).saveEmployee(employees.get(0));
Here we are instructing to mockito that when dao.saveEmployee(Employee) will be called, an exception should be thrown. This case is a simulation of failed data persist operation due to existing user.
3) findAllEmployees(): This is an example of Non void method stubbing.
when(dao.findAllEmployees()).thenReturn(employees);
Here we are instructing to mockito that when dao.findAllEmployees() will be called, a pre-populated employees list should be return instead.

Important!!!

Notice that above two tests [saveEmployee, saveExistingEmployee] are example of stubbing void methods while the last one[findAllEmployees] is an example of stubbing non void methods.
It’s important to understand that void methods can not be stubbed using usual when(x.m()).then() construct [as in findAllEmployees()] as compiler complains about calling a void method inside when. To get rid off this problem, you can use any of doNothing(), doThrow(),doAnswer(),doReturn() constructs as we did in [saveEmployee, saveExistingEmployee] tests.
That’s it.

No comments:

Post a Comment