🎭 Mockito Cheatsheet Completo 🎭

Mockito es un framework de mocking para pruebas unitarias en Java. Permite a los desarrolladores crear “objetos mock” (simulados) que reemplazan a las dependencias reales de la clase que se está probando. Esto asegura que las pruebas unitarias sean aisladas, deterministas y rápidas.


1. 🌟 Conceptos Clave


2. 🛠️ Configuración Inicial (Maven)

Para usar Mockito con JUnit 5 (recomendado), necesitas las siguientes dependencias en tu pom.xml:

<dependencies>
    &lt;!-- JUnit Jupiter API y Engine -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.10.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.10.0</version>
        <scope>test</scope>
    </dependency>

    &lt;!-- Mockito Core -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>5.5.0</version> &lt;!-- Usar la versión más reciente -->
        <scope>test</scope>
    </dependency>
    &lt;!-- Mockito JUnit Jupiter Integration (para anotaciones @Mock, @InjectMocks con JUnit 5) -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>5.5.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <source>17</source>
                <target>17</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.1.2</version>
        </plugin>
    </plugins>
</build>

3. 📝 Estructura Básica de una Prueba con Mockito

// src/main/java/com/example/myapp/UserRepository.java (Dependencia)
package com.example.myapp;

import java.util.Optional;

public class User {
    private Long id;
    private String name;
    // Constructor, getters, setters
    public User(Long id, String name) { this.id = id; this.name = name; }
    public Long getId() { return id; }
    public String getName() { return name; }
}

public interface UserRepository { // Interfaz de la dependencia (buena práctica para mocks)
    Optional<User> findById(Long id);
    void save(User user);
    boolean delete(Long id);
}

// src/main/java/com/example/myapp/UserService.java (Clase a probar)
package com.example.myapp;

import java.util.Optional;

public class UserService {
    private final UserRepository userRepository; // Dependencia

    public UserService(UserRepository userRepository) { // Inyección por constructor
        this.userRepository = userRepository;
    }

    public User getUserById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -&gt; new IllegalArgumentException("Usuario no encontrado con ID: " + id));
    }

    public boolean createUser(User user) {
        if (userRepository.findById(user.getId()).isPresent()) {
            throw new IllegalArgumentException("Usuario con este ID ya existe.");
        }
        userRepository.save(user);
        return true;
    }

    public boolean deleteUser(Long id) {
        return userRepository.delete(id);
    }
}
// src/test/java/com/example/myapp/UserServiceTest.java
package com.example.myapp;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; // Para usar @Mock, @InjectMocks
import org.mockito.Mock; // Para crear mocks
import org.mockito.InjectMocks; // Para inyectar mocks
import org.mockito.junit.jupiter.MockitoExtension; // Habilita las anotaciones Mockito

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*; // Aserciones de JUnit
import static org.mockito.Mockito.*; // Métodos estáticos de Mockito (when, verify)

@ExtendWith(MockitoExtension.class) // Habilita el uso de anotaciones Mockito en JUnit 5
class UserServiceTest {

    @Mock // Crea un objeto mock de UserRepository
    private UserRepository userRepository;

    @InjectMocks // Inyecta el/los mocks creados (@Mock) en una instancia de UserService
    private UserService userService; // Esta es la clase que estamos probando

    // @BeforeEach // Si no usas @InjectMocks, podrías inicializar así:
    // void setUp() {
    //    userRepository = mock(UserRepository.class); // Inicialización manual del mock
    //    userService = new UserService(userRepository); // Inyección manual
    // }

    @Test
    void testGetUserById_exists() {
        // Arrange (Preparación): Stubbing (simulación del comportamiento del mock)
        Long userId = 1L;
        User expectedUser = new User(userId, "Test User");
        when(userRepository.findById(userId)).thenReturn(Optional.of(expectedUser)); // Cuando findById se llama con 1L, devuelve Optional con expectedUser

        // Act (Ejecución): Llamar al método de la clase a probar
        User actualUser = userService.getUserById(userId);

        // Assert (Verificación): Comprobar el resultado
        assertNotNull(actualUser);
        assertEquals(expectedUser.getId(), actualUser.getId());
        assertEquals(expectedUser.getName(), actualUser.getName());

        // Verify (Verificación): Comprobar si el método del mock fue llamado
        verify(userRepository, times(1)).findById(userId); // Verifica que findById fue llamado exactamente 1 vez con userId
        verifyNoMoreInteractions(userRepository); // Verifica que no hubo más interacciones con el mock
    }

    @Test
    void testGetUserById_notFound() {
        // Arrange:
        Long userId = 99L;
        when(userRepository.findById(userId)).thenReturn(Optional.empty()); // Simula que el usuario no existe

        // Act & Assert: Verifica que se lanza la excepción correcta
        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -&gt; {
            userService.getUserById(userId);
        }, "Debería lanzar IllegalArgumentException si el usuario no es encontrado");

        assertEquals("Usuario no encontrado con ID: " + userId, thrown.getMessage());
        verify(userRepository, times(1)).findById(userId);
    }

    @Test
    void testCreateUser_success() {
        // Arrange:
        User newUser = new User(5L, "New User");
        when(userRepository.findById(newUser.getId())).thenReturn(Optional.empty()); // Simula que el ID no existe
        // Mockito.doNothing().when(userRepository).save(any(User.class)); // Opcional: si save() no devuelve nada y quieres ser explícito

        // Act:
        boolean created = userService.createUser(newUser);

        // Assert:
        assertTrue(created);

        // Verify: Verifica que save() fue llamado con el nuevo usuario
        verify(userRepository, times(1)).findById(newUser.getId());
        verify(userRepository, times(1)).save(newUser);
    }

    @Test
    void testDeleteUser_success() {
        // Arrange:
        Long userId = 1L;
        when(userRepository.delete(userId)).thenReturn(true); // Simula que la eliminación fue exitosa

        // Act:
        boolean deleted = userService.deleteUser(userId);

        // Assert:
        assertTrue(deleted);

        // Verify:
        verify(userRepository, times(1)).delete(userId);
    }
}

4. 🧮 Stubbing (Configurar Comportamiento)

Métodos para decirle a un mock qué hacer cuando se invoca un método.

4.1. Argument Matchers (Coincidencia de Argumentos)

Para stubbear o verificar llamadas con argumentos que varían.


5. ✅ Verificación (Comprobar Interacciones)

Métodos para verificar que los métodos de un mock fueron llamados como se esperaba.

5.1. Captura de Argumentos (ArgumentCaptor)

Para verificar los valores exactos de los argumentos pasados a un método, incluso si no se pueden comparar directamente o si necesitas inspeccionar el objeto.

import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.verify;

@Test
void testCapturaArgumento() {
    // Arrange:
    ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
    userService.createUser(new User(10L, "Captured User"));

    // Verify:
    verify(userRepository).save(userCaptor.capture()); // Captura el argumento pasado a save()

    User capturedUser = userCaptor.getValue(); // Obtiene el objeto capturado
    assertEquals("Captured User", capturedUser.getName());
}

6. 🕵️ Spies (Espías)

Para objetos reales que quieres monitorear o modificar parcialmente.

// src/main/java/com/example/myapp/EmailService.java (Clase real, no una interfaz)
package com.example.myapp;

public class EmailService {
    public boolean sendEmail(String to, String subject, String body) {
        System.out.println(String.format("Enviando email a %s con asunto %s", to, subject));
        // Lógica real de envío de email
        return true;
    }

    public String formatSubject(String originalSubject) {
        return "RE: " + originalSubject;
    }
}
// src/test/java/com/example/myapp/EmailServiceTest.java
package com.example.myapp;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Spy; // Para crear spys
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class EmailServiceTest {

    @Spy // Crea un spy de EmailService (usa la instancia real, no un mock)
    private EmailService emailService; // Esto creará una instancia real de EmailService

    @Test
    void testSendEmailSpy() {
        String recipient = "test@example.com";
        String subject = "Hola";
        String body = "Contenido de prueba";

        // Stubbing de un método: sendEmail se comportará como si fuera mockeado
        doReturn(false).when(emailService).sendEmail(recipient, subject, body);

        // La llamada a sendEmail ahora devolverá 'false' según el stub
        boolean result = emailService.sendEmail(recipient, subject, body);
        assertFalse(result);

        // Sin embargo, formatSubject() seguirá ejecutando la lógica real
        String formatted = emailService.formatSubject("Original");
        assertEquals("RE: Original", formatted);

        // Puedes verificar interacciones con el spy
        verify(emailService, times(1)).sendEmail(recipient, subject, body);
        verify(emailService, times(1)).formatSubject("Original");
    }
}

7. 💡 Buenas Prácticas y Consejos


Este cheatsheet te proporciona una referencia completa de Mockito, cubriendo sus conceptos esenciales, cómo configurar mocks y spys, realizar stubbing, verificar interacciones y aplicar las mejores prácticas para escribir pruebas unitarias de Java efectivas y robustas.