Home Arrow Icon Knowledge base Arrow Icon Global Arrow Icon Can you provide an example of using an argument captor with collections in Mockito


Can you provide an example of using an argument captor with collections in Mockito


Using an `ArgumentCaptor` with collections in Mockito is a powerful way to verify that specific arguments were passed to a method, especially when those arguments are collections. Here's a detailed example of how to do this:

Example Scenario

Suppose you have a service class `StudentService` that uses a repository `StudentRepository` to save students. The `saveStudents` method in `StudentRepository` takes a collection of `Student` objects.

java
public class StudentRepository {
    public void saveStudents(List students) {
        // Implementation to save students
    }
}

public class StudentService {
    private final StudentRepository studentRepository;

    public StudentService(StudentRepository studentRepository) {
        this.studentRepository = studentRepository;
    }

    public void saveStudents(List students) {
        studentRepository.saveStudents(students);
    }
}

Using ArgumentCaptor with Collections

To test the `saveStudents` method in `StudentService`, you can use `ArgumentCaptor` to capture the collection of students passed to the `saveStudents` method in `StudentRepository`.

1. Mock the Repository: First, you need to mock the `StudentRepository`.

2. Create ArgumentCaptor: Create an `ArgumentCaptor` for the type of collection you want to capture, in this case, `List`.

3. Call the Method: Call the method on the service that you want to test.

4. Verify and Capture: Use `Mockito.verify` to verify that the method was called and capture the arguments.

5. Assert Captured Values: Finally, assert that the captured values match your expectations.

Here's how you can implement this:

java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentCaptor.forClass;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
public class StudentServiceTest {

    @Mock
    private StudentRepository studentRepository;

    @InjectMocks
    private StudentService studentService;

    @Test
    void testSaveStudents() {
        // Create students
        Student student1 = new Student(1, "Harry");
        Student student2 = new Student(2, "Tae");

        // Create a list of students
        List students = List.of(student1, student2);

        // Call the method to save students
        studentService.saveStudents(students);

        // Create an ArgumentCaptor for List
        ArgumentCaptor> studentCaptor = forClass(List.class);

        // Verify that saveStudents was called and capture the argument
        verify(studentRepository).saveStudents(studentCaptor.capture());

        // Get the captured list of students
        List capturedStudents = studentCaptor.getValue();

        // Assert that the captured students match the expected students
        assertEquals(students, capturedStudents);
    }
}

However, due to Java's type erasure, capturing generic types like `List` directly can be tricky. Mockito doesn't perform type checks, so you might need to work around this by capturing the raw type (`List.class`) and then asserting the contents manually.

Capturing Generic Collections

If you need to capture a generic collection like `List`, you can capture it as a raw `List` and then manually verify its contents:

java
// Create an ArgumentCaptor for List
ArgumentCaptor studentCaptor = forClass(List.class);

// Verify that saveStudents was called and capture the argument
verify(studentRepository).saveStudents(studentCaptor.capture());

// Get the captured list
List capturedStudents = studentCaptor.getValue();

// Manually assert each student in the captured list
assertEquals(2, capturedStudents.size());
assertEquals(student1, capturedStudents.get(0));
assertEquals(student2, capturedStudents.get(1));

This approach allows you to verify that the correct collection was passed, even though Mockito doesn't handle generics perfectly.

Conclusion

Using `ArgumentCaptor` with collections in Mockito is a powerful tool for verifying that specific arguments were passed to methods. While there are limitations with generic types, you can still effectively use it by capturing raw types and manually asserting the contents.

Citations:
[1] https://springframework.guru/argumentcaptor-in-mockito/
[2] https://www.youtube.com/watch?v=fQMRWvgoaCw
[3] https://groups.google.com/g/mockito/c/JsMU-_cOIs8
[4] https://www.digitalocean.com/community/tutorials/mockito-argumentcaptor-captor-annotation
[5] https://www.linkedin.com/pulse/improving-tests-argumentcaptor-benefits-advantages-souza
[6] https://stackoverflow.com/questions/47565887/how-to-capture-arguments-which-are-functions-using-argumentcaptor-mockito/47566488
[7] https://site.mockito.org/javadoc/current/org/mockito/ArgumentCaptor.html
[8] https://dev.to/pathus90/understanding-argumentcaptor-in-mockito-a-comprehensive-guide-2ehe
[9] https://mincong.io/2019/12/15/mockito-argument-captor/
[10] https://jdriven.com/blog/2012/10/mockito-using-argumentcaptor-for-generic-typed-collections