Mockito @Captor Annotation Tutorial

Introduction

The @Captor annotation in Mockito is used to create an argument captor, which is a special type of object that can capture argument values passed to mock methods. This is particularly useful when you want to verify that a method was called with specific arguments or to inspect the arguments passed to a mock method. In this tutorial, we will demonstrate step by step how to use the @Captor annotation in Mockito.

Maven Dependencies

To use Mockito with JUnit 5, add the following dependencies to your pom.xml file:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>4.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.9.2</version>
    <scope>test</scope>
</dependency>

Example Scenario

We will create a UserService class that has a dependency on a UserRepository. Our goal is to test the UserService methods using Mockito's @Captor annotation to capture and verify the arguments passed to the UserRepository methods.

UserService and UserRepository Classes

First, create the User class, the UserRepository interface, and the UserService class.

public class User {
    private String name;
    private String email;

    // Constructor, getters, and setters
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

public interface UserRepository {
    void saveUser(User user);
    User findUserByEmail(String email);
}

public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void registerUser(String name, String email) {
        User user = new User(name, email);
        userRepository.saveUser(user);
    }

    public User getUserByEmail(String email) {
        return userRepository.findUserByEmail(email);
    }
}

JUnit 5 Test Class with Mockito

Create a test class for UserService using JUnit 5 and Mockito.

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Captor
    private ArgumentCaptor<User> userCaptor;

    @Test
    public void testRegisterUser() {
        // Given
        String name = "John Doe";
        String email = "john.doe@example.com";

        // When
        userService.registerUser(name, email);

        // Then
        verify(userRepository).saveUser(userCaptor.capture());
        User capturedUser = userCaptor.getValue();
        assertEquals(name, capturedUser.getName());
        assertEquals(email, capturedUser.getEmail());
    }

    @Test
    public void testGetUserByEmail() {
        // Given
        String email = "jane.doe@example.com";
        User mockUser = new User("Jane Doe", email);
        when(userRepository.findUserByEmail(email)).thenReturn(mockUser);

        // When
        User result = userService.getUserByEmail(email);

        // Then
        assertNotNull(result);
        assertEquals("Jane Doe", result.getName());
        assertEquals(email, result.getEmail());
        verify(userRepository).findUserByEmail(email);
    }
}

Explanation

  1. Annotations:

    • @ExtendWith(MockitoExtension.class): Integrates Mockito with JUnit 5, enabling the use of Mockito annotations.
    • @Mock: Creates a mock instance of the UserRepository interface. This mock will be used to simulate the behavior of the actual repository without needing a concrete implementation.
    • @InjectMocks: Injects the mock UserRepository into the UserService instance.
    • @Captor: Creates an ArgumentCaptor for capturing User objects.
  2. testRegisterUser():

    • Given: Sets up the name and email for a new user.
    • When: Calls the registerUser method on the UserService instance.
    • Then:
      • Verifies that the saveUser method was called on the UserRepository with a User object.
      • Captures the User object passed to the saveUser method using the userCaptor.
      • Asserts that the captured user's name and email are correct.
  3. testGetUserByEmail():

    • Given: Sets up a mock User object and configures the UserRepository to return the mock user when the findUserByEmail method is called.
    • When: Calls the getUserByEmail method on the UserService instance.
    • Then:
      • Asserts that the returned user is not null and that the name and email are correct.
      • Verifies that the findUserByEmail method was called on the UserRepository with the expected email.

In the above code example, the UserRepository interface does not have an implementation class. This is intentional because Mockito is used to create a mock of the UserRepository. The mock simulates the behavior of the UserRepository without needing an actual implementation.

When using Mockito, you do not need a concrete implementation of the interface you are mocking. Mockito will generate a mock object that implements the interface and allows you to specify the behavior of its methods.

Conclusion

The @Captor annotation in Mockito allows you to capture and inspect arguments passed to mock methods, providing more control and insight into your unit tests. By using @Captor, you can verify that methods are called with the correct arguments and inspect the values passed to them. This step-by-step guide demonstrated how to effectively use the @Captor annotation in your unit tests, covering different scenarios to ensure comprehensive testing of the UserService class.

Related Mockito Annotations

Comments