Mockito ArgumentCaptor Example

0

Consider the scenario where your method depends on one or more collaborators whose behavior you have stubbed. Now suppose somewhere in your method, you have create an internal object, composed of few attributes, which in turn you are passing to one of these collaborators.

The attributes might be something you have entirely manufactured or may be the return values of the collaborators.

How do you verify that the collaborator has received the right argument object which also means that we want to ensure the attributes are all correct?

Mockito provides ArgumentCaptor which one can use with verification and then call ArgumentCaptor.getValue() to the assert the captured argument value.

System Under Test

We want to calculate net pay of an employee. The method that takes care of this is Employee.calculateNetPay(). It in turn depends on EmployeeManager and TaxManager.
EmployeeManager.getSalaryStructure(employee) returns us the SalaryStructure of the employee. Next, we call TaxManager.calculateTax(employee, salaryStructure) to find out the tax that needs to be deducted. Once the tax calculation process is done, we record an event EmpEvent and return the net pay.

empManager.recordEvent(new EmpEvent(this, EventType.TAX_CALCULATED, tax));

Our goal would be to make sure that the event is recorded and verify its attributes.

Employee:

package com.javarticles.mokcito;

public class Employee {
    private TaxManager taxManager;
    private EmployeeManager empManager;
    private String name;
    
    public Employee(String name) {
       this.name = name;
    }   
    
    public void setTaxManager(TaxManager taxManager) {
        this.taxManager = taxManager;
    }

    public void setEmpManager(EmployeeManager empManager) {
        this.empManager = empManager;
    }

    public Long calculateNetPay() {
        SalaryStructure salaryStructure = empManager.getSalaryStructure(this);
        Long tax = taxManager.calculateTax(this, salaryStructure);
        empManager.recordEvent(new EmpEvent(this, EventType.TAX_CALCULATED, tax));
        return salaryStructure.getGross() - tax;
    }
    
    public String toString() {
        return "Employee (" + name + ")";
    }       
    
    public String getName() {
        return name;
    }

    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (!(o instanceof Employee)) {
            return false;
        }
        Employee emp = (Employee) o;
        return name.equals(emp.getName());
    }
}

EmployeeManager:

package com.javarticles.mokcito;

public interface EmployeeManager {
    SalaryStructure getSalaryStructure(Employee employee);

    void recordEvent(EmpEvent empEvent);
}

Salary structure is kept very simple.

SalaryStructure:

package com.javarticles.mokcito;

public class SalaryStructure {
    private Long gross;
    public SalaryStructure(Long gross) {
        this.gross = gross;
    }
    public Long getGross() {
        return gross;
    }
}

EventType:

package com.javarticles.mokcito;

public enum EventType {
    TAX_CALCULATED;
}

We need to verify the employee event attributes which contains employee, event type and the tax amount as data.

EmpEvent:

package com.javarticles.mokcito;

public class EmpEvent {
    private Employee emp;
    private EventType eventType;
    private Object data;
    public EmpEvent(Employee employee, EventType eventType, Object data) {
        this.emp = employee;
        this.eventType = eventType;
        this.data = data;
    }
    public Employee getEmp() {
        return emp;
    }
    public EventType getEventType() {
        return eventType;
    }
    public Object getData() {
        return data;
    }
}

We will use @Mock annotation to create mock objects EmployeeManager> and TaxManager. See @Mock Annotation Example for more details about @Mock annotation.
In init(), we first initialize Mockito annotated members.

MockitoAnnotations.initMocks(this);

Next, we create an employee and set the collaborators.

emp = new Employee("Joe");
emp.setEmpManager(empManager);
emp.setTaxManager(taxManager);

We now need to stub employee manager to return a salary structure.

salaryStructure = new SalaryStructure(12000L);               
when(empManager.getSalaryStructure(emp)).thenReturn(salaryStructure);

Next stub tax manager’s calculateTax to return us some tax amount.

when(taxManager.calculateTax(emp, salaryStructure)).thenReturn(1000L);

If you note, we have create an entirely new employee object. It is not the same as the one we have used in stubbing the methods but it still works as Mockito verifies argument values using an equals() method.
Since we have implemented Employee.equal() based on the name attribute and name being same in both employee objects, the stubbing works.
In our first test, we calculate employee’s net pay and then verify that the employeeManager.recordEvent() is called. Since we have not implemented EmpEvent.equals(), we have used any(EmpEvent.class) to make sure that the argument is of type EmpEvent, thus it matches any EmpEvent object.

verify(empManager).recordEvent(any(EmpEvent.class));

MockitoArgumentCaptorTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.*;

import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

import com.javarticles.mokcito.EmpEvent;
import com.javarticles.mokcito.Employee;
import com.javarticles.mokcito.EmployeeManager;
import com.javarticles.mokcito.EventType;
import com.javarticles.mokcito.SalaryStructure;
import com.javarticles.mokcito.TaxManager;

public class MockitoArgumentCaptorTests {
    @Mock private EmployeeManager empManager;
    @Mock private TaxManager taxManager;
    private Employee emp;
    private SalaryStructure salaryStructure;
    
    @BeforeMethod
    public void init() {
        MockitoAnnotations.initMocks(this);
        emp = new Employee("Joe");
        emp.setEmpManager(empManager);
        emp.setTaxManager(taxManager);
        
        salaryStructure = new SalaryStructure(12000L);               
        when(empManager.getSalaryStructure(emp)).thenReturn(salaryStructure);
        when(taxManager.calculateTax(emp, salaryStructure)).thenReturn(1000L);
    }

    @Test
    public void argumentVerification() {                
        Employee empJoe = new Employee("Joe");
        empJoe.setEmpManager(empManager);
        empJoe.setTaxManager(taxManager);
        Long netPay = empJoe.calculateNetPay();
        
        assertEquals(netPay, new Long(11000));
        verify(empManager).getSalaryStructure(emp);
        verify(taxManager).calculateTax(empJoe, salaryStructure);
        verify(empManager).recordEvent(any(EmpEvent.class));
    }
}

In argumentValuesVerificationWithoutArgumentCapture(), we try to verify a specific EmpEvent object but the test fails with an exception.

FAILED: argumentValuesVerificationWithoutArgumentCapture
Argument(s) are different! Wanted:
empManager.recordEvent(
    [email protected]
);
-> at com.javarticles.mockito.MockitoArgumentCaptorTests.argumentValuesVerificationWithoutArgumentCapture(MockitoArgumentCaptorTests.java:61)
Actual invocation has different arguments:
empManager.recordEvent(
    [email protected]
);
-> at com.javarticles.mokcito.Employee.calculateNetPay(Employee.java:23)

MockitoArgumentCaptorTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.*;

import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

import com.javarticles.mokcito.EmpEvent;
import com.javarticles.mokcito.Employee;
import com.javarticles.mokcito.EmployeeManager;
import com.javarticles.mokcito.EventType;
import com.javarticles.mokcito.SalaryStructure;
import com.javarticles.mokcito.TaxManager;

public class MockitoArgumentCaptorTests {
    @Mock private EmployeeManager empManager;
    @Mock private TaxManager taxManager;
    private Employee emp;
    private SalaryStructure salaryStructure;
    
    @BeforeMethod
    public void init() {
        MockitoAnnotations.initMocks(this);
        emp = new Employee("Joe");
        emp.setEmpManager(empManager);
        emp.setTaxManager(taxManager);
        
        salaryStructure = new SalaryStructure(12000L);               
        when(empManager.getSalaryStructure(emp)).thenReturn(salaryStructure);
        when(taxManager.calculateTax(emp, salaryStructure)).thenReturn(1000L);
    }
   
    @Test
    public void argumentVerification() {                
        Employee empJoe = new Employee("Joe");
        empJoe.setEmpManager(empManager);
        empJoe.setTaxManager(taxManager);
        Long netPay = empJoe.calculateNetPay();
        
        assertEquals(netPay, new Long(11000));
        verify(empManager).getSalaryStructure(emp);
        verify(taxManager).calculateTax(empJoe, salaryStructure);
        verify(empManager).recordEvent(any(EmpEvent.class));
    }
    
    @Test
    public void argumentValuesVerificationWithoutArgumentCapture() {                
        Employee empJoe = new Employee("Joe");
        empJoe.setEmpManager(empManager);
        empJoe.setTaxManager(taxManager);
        Long netPay = empJoe.calculateNetPay();
        
        assertEquals(netPay, new Long(11000));
        verify(empManager).getSalaryStructure(emp);
        verify(taxManager).calculateTax(empJoe, salaryStructure);
        verify(empManager).recordEvent(new EmpEvent(empJoe, EventType.TAX_CALCULATED, 1000L));
    }
}

Verifying argument value using ArgumentCaptor

In argumentValuesVerificationWithArgumentCapture(), we use ArgumentCaptor to verify the argument as well as its attributes.
First we need to create an ArgumentCaptor object for the type of argument we are interested in. We will use a factory method to create an instance of the argument captor for the EmpEvent type.

ArgumentCaptor<EmpEvent> empEventArgCaptor = ArgumentCaptor.forClass(EmpEvent.class);

We need the ArgumentCaptor object before we start the verifying. We call ArgumentCaptor.capture() to capture the argument. Internally it uses argument matcher and stores the argument value.

verify(empManager).recordEvent(empEventArgCaptor.capture());

We will retrieve the captured argument value using ArgumentCaptor.getValue().

assertEquals(empEventArgCaptor.getValue().getData(), 1000L);
assertEquals(empEventArgCaptor.getValue().getEventType(), EventType.TAX_CALCULATED);
assertEquals(empEventArgCaptor.getValue().getEmp(), empJoe);

MockitoArgumentCaptorTests:

package com.javarticles.mockito;

import static org.mockito.Mockito.*;

import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

import com.javarticles.mokcito.EmpEvent;
import com.javarticles.mokcito.Employee;
import com.javarticles.mokcito.EmployeeManager;
import com.javarticles.mokcito.EventType;
import com.javarticles.mokcito.SalaryStructure;
import com.javarticles.mokcito.TaxManager;

public class MockitoArgumentCaptorTests {
    @Mock private EmployeeManager empManager;
    @Mock private TaxManager taxManager;
    private Employee emp;
    private SalaryStructure salaryStructure;
    
    @BeforeMethod
    public void init() {
        MockitoAnnotations.initMocks(this);
        emp = new Employee("Joe");
        emp.setEmpManager(empManager);
        emp.setTaxManager(taxManager);
        
        salaryStructure = new SalaryStructure(12000L);               
        when(empManager.getSalaryStructure(emp)).thenReturn(salaryStructure);
        when(taxManager.calculateTax(emp, salaryStructure)).thenReturn(1000L);
    }
   
    @Test
    public void argumentVerification() {                
        Employee empJoe = new Employee("Joe");
        empJoe.setEmpManager(empManager);
        empJoe.setTaxManager(taxManager);
        Long netPay = empJoe.calculateNetPay();
        
        assertEquals(netPay, new Long(11000));
        verify(empManager).getSalaryStructure(emp);
        verify(taxManager).calculateTax(empJoe, salaryStructure);
        verify(empManager).recordEvent(any(EmpEvent.class));
    }
    
    @Test
    public void argumentValuesVerificationWithoutArgumentCapture() {                
        Employee empJoe = new Employee("Joe");
        empJoe.setEmpManager(empManager);
        empJoe.setTaxManager(taxManager);
        Long netPay = empJoe.calculateNetPay();
        
        assertEquals(netPay, new Long(11000));
        verify(empManager).getSalaryStructure(emp);
        verify(taxManager).calculateTax(empJoe, salaryStructure);
        verify(empManager).recordEvent(new EmpEvent(empJoe, EventType.TAX_CALCULATED, 1000L));
    }

    @Test
    public void argumentValuesVerificationWithArgumentCapture() {        
        Employee empJoe = new Employee("Joe");
        empJoe.setEmpManager(empManager);
        empJoe.setTaxManager(taxManager);
        empJoe.calculateNetPay();
        
        ArgumentCaptor<EmpEvent> empEventArgCaptor = ArgumentCaptor.forClass(EmpEvent.class);
        verify(empManager).recordEvent(empEventArgCaptor.capture());
        
        assertEquals(empEventArgCaptor.getValue().getData(), 1000L);
        assertEquals(empEventArgCaptor.getValue().getEventType(), EventType.TAX_CALCULATED);
        assertEquals(empEventArgCaptor.getValue().getEmp(), empJoe);
    }
}

Download the source code

This was an example about Mockito ArgumentCaptor.

You can download the source code here: mockitoArgumentCaptor.zip
Share.

Comments are closed.