ArgumentCaptor in Mockito

ArgumentCaptor in Mockito

0 Comments

ArgumentCaptor in Mockito allows you to capture arguments passed to methods for further assertions. You can apply standard JUnit assertion methods, such as assertEquals(), assertThat(), and so on, to perform assertions on the captured arguments. In Mockito, you will find the ArgumentCaptor class in the org. mockito package

If you are new to mocking with Mockito, I suggest you go through my earlier post Mocking in Unit Tests with Mockito

In this post, I will explain how to create an ArgumentCaptor, its important methods, and how to use them.

Dependency

To use Mockito, you’ll need to add the following dependency in your pom.xml.

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.23.4</version>
    <scope>test</scope>
</dependency>

Sample Application

The sample application is a simple Spring Boot application. It has a Student entity having id and name as its properties.

You can find the source code of the sample application here on Github.

The code of the Student entity class is this.

Student.java

@Entity
public class Student {

    @Id
    private int id;
    private String name;

    public Student() {
    }

    public Student(int id, String name) {
        Id = id;
        this.name = name;
    }

    public int getId() {
        return Id;
    }

    public void setId(int id) {
        Id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Student{ Id = " + Id + ", name = '" + name + '\'' + '}';
    }
}

The application also have a service implementation to save Student entities and a Spring Data JPA repository interface.

The code of the StudentRepository interface is this.

StudentRepository.java

package springframework.repository;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import springframework.domain.Student;

@Repository
public interface StudentRepository extends CrudRepository<Student, Integer> {
}

The code of the StudentService interface is this.

StudentService.java

package springframework.service;

import springframework.domain.Student;

public interface StudentService {
     Student saveStudent(Student student);
}

The implementation class is this.

StudentServiceImpl.java

package springframework.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import springframework.domain.Student;
import springframework.repository.StudentRepository;

@Service
public class StudentServiceImpl implements StudentService {
    private StudentRepository studentRepository;

    @Autowired
    public StudentServiceImpl(StudentRepository studentRepository) {

        this.studentRepository = studentRepository;
    }

    @Override
    public Student saveStudent(Student student) {
        return studentRepository.save(student);
    }
}

Setting up the Unit Test

I have created a test class ArgumentCaptorTest.java. The code is this

@RunWith(MockitoJUnitRunner.class)
public class ArgumentCaptorTest {

@Captor 
private ArgumentCaptor<Student> captor;

@Mock 
private StudentRepository studentRepository;

@InjectMocks
private StudentServiceImpl studentService;

In the code above, Line 1 runs the test class with @RunWith(MockitoJUnitRunner.class) to make Mockito detect ArgumentCaptor, which we will declare next.

Line 2 – Line 3 creates an ArgumentCaptor of type Student and annotates it with @Captor to store captured argument.

Line 7 – Line 11 uses the @Mock annotation to mock StudentRepository, which is then automatically injected into our StudentServiceImpl with the @InjectMocks annotation.

Test Case to show ArgumentCapture Usage

Now, I will show the usage of ArgumentCaptor. This is the code.

@Test
public void shouldCapture() {

    Student  student1 = new Student(1, "Harry");
    studentService.saveStudent(student1);

    Mockito.verify(studentRepository).save(captor.capture());

    assertEquals("Harry", captor.getValue().getName());
    assertEquals(1,captor.getValue().getId());

}

To capture the method arguments, you need to use the capture() method of ArgumentCaptor. You should call it during the verification phase of the test.

In the code provided above, Line 4 – Line 5 creates and saves a Student object student1.

Line 7 calls Mockito.verify() to verify if the save() method of the mocked StudentRepository has been called. Then the call to captor.capture() captures the method argument passed to the mock method.

Line 9 – Line 10 perform assertions by calling the getValue() method of ArgumentCaptor to get the captured value of the argument.

Note: If the verified methods are called multiple times, then the getValue() method will return the latest captured value.

Now, let’s run our test.

This figure shows that the test case has passed successfully.
Test Output of Mockito ArgumentCapture

Multiple Captures using ArgumentCaptor

In the previous test case, we captured only one value, since there was only one verify method. To capture multiple argument values, ArgumentCaptor provides the getAllValues() method.

The test code is this.

@Test
public void shouldCaptureMultipleTimes() {

    Student student1 = new Student(1, "Harry");
    Student student2 = new Student(2, "Tae");
    Student student3 = new Student(3, "Louis");

    studentService.saveStudent(student1);
    studentService.saveStudent(student2);
    studentService.saveStudent(student3);

    Mockito.verify(studentRepository,Mockito.times(3)).save(captor.capture());

   List studentList = captor.getAllValues();
   
   assertEquals("Harry", studentList.get(0).getName());
   assertEquals("Tae", studentList.get(1).getName());
   assertEquals("Louis", studentList.get(2).getName());
}

In the code provided above, Line 4 – Line 10 creates and saves three Student objects named student1, student2, and student3.

Then, Line 12 calls the Mockito.verify() method to check that the StudentRepository.save() is called thrice. Additionally, the call to captor.capture() captures the three values.

Line 14 obtains the list of captured values by the getValues() method. If you call verify methods multiple times, the getAllValue() method will return the merged list of all the values from all invocations.

Finally, the assertEquals() methods perform assertions on the captured arguments.

The output of the test is this.

Test Output Multiple Arguments with ArgumentCaptor

The ArgumentCaptor.forClass() Method

Till now we have used the @Captor annotation to instantiate ArgumentCaptor in our test. Alternatively, you can use the ArgumentCaptor.forClass() method for the same purpose.

The test code is this.

ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Student.class);
@Test
public void shouldCaptureManually() {

    Student student1 = new Student(1, "Harry");
    studentService.saveStudent(student1);

    Mockito.verify(studentRepository).save(argumentCaptor.capture());
    Student captured = argumentCaptor.getValue();

    assertEquals("Harry", captured.getName());

}

On running the test, you should see the test passing successfully.

ArgumentCaptor Initialization

Summary

I have seen test code using ArgumentCaptor with stubbing, an approach that I don’t advocate.

Let’s say, you use ArgumentCaptor during stubbing with Mockito.when, like this.

UserRegistration userRegistration = new UserRegistration ("Jammie", "[email protected]", 23);
Mockito.when(userRegistrationService.registerUser(registrationCaptor.capture())).thenReturn(userRegistration); 
assertTrue(userService.register(userRegistration)); 
assertEquals(userRegistration, registrationCaptor.getValue());

This code decreases readability as compared to the conventional way of using Mockito.eq(userRegistration). Additionally, the call to registrationCaptor.capture() during stubbing lack clarity of its intent. Also, you end up with an extra assertion – the final assertEuals(), which you wouldn’t have required with the conventional Mockito.eq(userRegistration).

In addition, suppose userService.register() doesn’t call userRegistrationService.register() in the actual code. In this scenario, you will get this exception

org.mockito.exceptions.base.MockitoException: 
No argument value was captured!

This exception message is confusing and can trip you to believing that you have issues in your test. However, the issue is in the code you are testing and not in your test.

So the best practice is to use ArgumentCaptor the way it is designed for – during verification.

You can find the source code of this post on Github.

For in-depth knowledge on Testing in Spring Boot Applications check my Udemy Best Seller Course Testing Spring Boot: Beginner to Guru.

Testing Spring Boot Online Course

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.