CodeGym /Cursos /Módulo 5. Spring /Lección 133: Mockito para crear mocks y stubs

Lección 133: Mockito para crear mocks y stubs

Módulo 5. Spring
Nivel 9 , Lección 2
Disponible

Cuando pruebas una aplicación, a menudo pasa que tu código depende de otros componentes, como servicios, repositorios, APIs externas o incluso librerías de terceros. En vez de ejecutar el código real de esas dependencias en los tests (y gastar un montón de recursos), podemos usar mocks — "trucos" que imitan el comportamiento de los objetos reales. Y aquí entra en escena Mockito.

Imagina esto: estás escribiendo un test para un servicio que consulta la base de datos. Una llamada real a la BD podría tardar demasiado, y además podría crear una dependencia fuerte de SQL... esas cosas pasan. En lugar de eso, usando Mockito, simplemente sustituimos la llamada a la base por un objeto especial que devolverá un resultado preparado de antemano. ¿Cómodo? ¡Claro que sí!


Conceptos básicos de Mockito

Antes de pasar a la práctica, aclaremos qué hay detrás de los términos mocks, stubs y spies.

  • Mock (Mock) — es un objeto que imita el comportamiento de un objeto real. Gracias a él podemos definir respuestas predefinidas para llamadas concretas.
  • Stub (Stub) — parecido a un mock, pero menos flexible. Es simplemente un objeto "limpio" con comportamiento definido de antemano, que se usa para las pruebas.
  • Spy (Spy) — es un objeto real, pero con la opción de espiar cómo se usa. A veces necesitas la funcionalidad real del objeto, pero poder sustituir partes de su comportamiento.

Empezando con Mockito

Para usar Mockito, añadamos su dependencia al proyecto.

Conectar Mockito vía Maven


<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.0.0</version> <!-- Sustituye por la versión actual -->
    <scope>test</scope>
</dependency>

Si usas Gradle:


testImplementation 'org.mockito:mockito-core:5.0.0' // Sustituye por la versión actual

También merece la pena añadir JUnit si aún no está en el proyecto.


Principales funcionalidades de Mockito

1. Crear mocks

Puedes crear un mock con el método mock():


import static org.mockito.Mockito.*;

SomeService someServiceMock = mock(SomeService.class);

Sin embargo, si usas JUnit 5, recomendamos usar la anotación @Mock para mayor legibilidad. Para ello necesitarás la anotación @ExtendWith(MockitoExtension.class).

Ejemplo:


import static org.mockito.Mockito.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class SomeServiceTest {

    @Mock
    private SomeService someServiceMock;

    // Tus tests aquí
}

2. Configurar el comportamiento de los mocks

Después de crear el mock, lo interesante es definir su comportamiento. Por ejemplo, queremos que un método devuelva un valor específico:


when(someServiceMock.someMethod()).thenReturn("Mocked Value");

Ejemplo de uso:


@Test
void testMockBehavior() {
    when(someServiceMock.greet("John")).thenReturn("Hello, mocked John!");

    String result = someServiceMock.greet("John");

    assertEquals("Hello, mocked John!", result);
}
La llamada when().thenReturn() te permite literalmente "enseñar" al mock qué debe hacer.

3. Verificación de llamadas

Mockito no solo permite definir comportamiento, sino también verificar si se llamó un método y cuántas veces.


@Test
void testMethodInvocation() {
    someServiceMock.greet("John");

    verify(someServiceMock).greet("John"); // Comprobación: ¿se llamó el método greet con el parámetro "John"?
    verify(someServiceMock, times(1)).greet("John"); // Comprobación: se llamó exactamente una vez
}
Si el método no fue llamado, el test fallará con un error como: "Wanted but not invoked".

Anotaciones de Mockito

Para simplificar el código, Mockito ofrece varias anotaciones útiles:

  1. @Mock: Crea un mock para el objeto indicado.
    
    @Mock
    private SomeService someServiceMock;
    
  2. @InjectMocks: Inyecta automáticamente los mocks en las dependencias del objeto que se está probando.
    
    @InjectMocks
    private SomeController someController;
    

    Ejemplo:

    
    @Test
    void testController() {
        when(someServiceMock.greet("John")).thenReturn("Hello, John!");
    
        String response = someController.greet("John");
    
        assertEquals("Hello, John!", response);
    }
    
  3. @Spy: Crea un objeto real, pero permite sustituir algunos de sus métodos.
    
    @Spy
    private SomeService realService;
    

Práctica: probar un servicio

Tenemos un servicio GreetingService que depende de TimeProvider para formar el saludo, teniendo en cuenta la hora actual:


public class GreetingService {
    private final TimeProvider timeProvider;

    public GreetingService(TimeProvider timeProvider) {
        this.timeProvider = timeProvider;
    }

    public String getGreeting(String name) {
        int currentHour = timeProvider.getCurrentHour();

        if (currentHour < 12) {
            return "Good morning, " + name + "!";
        } else {
            return "Good afternoon, " + name + "!";
        }
    }
}

En los tests mockeamos TimeProvider para comprobar el comportamiento del servicio:


import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class GreetingServiceTest {

    private final TimeProvider timeProviderMock = mock(TimeProvider.class);
    private final GreetingService greetingService = new GreetingService(timeProviderMock);

    @Test
    void testMorningGreeting() {
        when(timeProviderMock.getCurrentHour()).thenReturn(9); // Mañana

        String greeting = greetingService.getGreeting("John");

        assertEquals("Good morning, John!", greeting);
    }

    @Test
    void testAfternoonGreeting() {
        when(timeProviderMock.getCurrentHour()).thenReturn(15); // Tarde

        String greeting = greetingService.getGreeting("John");

        assertEquals("Good afternoon, John!", greeting);
    }
}
Aquí TimeProvider se puede considerar una dependencia externa que hemos mockeado.

Errores comunes al trabajar con Mockito

Algunos fallos pueden arruinar tus tests inesperadamente:

  1. NullPointerException: Te olvidas de inicializar los mocks. Solución: usa @Mock y @ExtendWith(MockitoExtension.class).
  2. Desajuste entre comportamiento y llamada: Si el método se llama con parámetros distintos a los que especificaste en when(). Revisa siempre los argumentos.
  3. Comportamiento excesivo en los mocks: "Enseña" a los mocks solo lo que necesitas para ese test concreto; si no, el test será difícil de mantener.

Mockito es una herramienta potente que hace que probar sistemas complejos sea más fácil y cómodo. Ahora sabes cómo crear mocks, configurar su comportamiento y verificar interacciones. En las siguientes lecciones verás cómo aplicar todo esto para probar controllers y otros componentes de aplicaciones Spring.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION