Spring Boot

SpringBoot

Tutorial SpringBoot: Testing de aplicaciones reactivas WebFlux

Utiliza WebTestClient y StepVerifier para desarrollar pruebas de software de aplicaciones reactivas de Spring WebFlux en Spring Boot, maneja la reactividad de forma robusta.

Aprende SpringBoot GRATIS y certifícate

reactor-test en Spring WebFlux

El módulo reactor-test proporciona un conjunto de herramientas esenciales para realizar pruebas unitarias en aplicaciones reactivas con Spring WebFlux. Estas herramientas facilitan la simulación y verificación de flujos reactivos, permitiendo asegurar el correcto funcionamiento de Mono y Flux en diversos escenarios.

Una de las principales utilidades es el StepVerifier, que permite definir expectativas sobre las emisiones y eventos de un flujo reactivo. Con StepVerifier, se pueden crear pruebas detalladas que verifiquen secuencias de datos, errores y completitud de los flujos.

Flux<String> flujo = Flux.just("Alpha", "Beta", "Gamma");

StepVerifier.create(flujo)
    .expectNext("Alpha")
    .expectNext("Beta")
    .expectNext("Gamma")
    .verifyComplete();

Para pruebas que involucran operaciones temporizadas, el VirtualTimeScheduler es indispensable. Esta herramienta permite manipular el tiempo virtualmente, acelerando la ejecución de flujos que dependen de retrasos o intervalos sin afectar el tiempo real de ejecución de las pruebas.

import reactor.test.scheduler.VirtualTimeScheduler;

VirtualTimeScheduler virtualTimeScheduler = VirtualTimeScheduler.getOrSet();

Flux<Long> flujoIntervalo = Flux.interval(Duration.ofSeconds(5))
    .take(2);

StepVerifier.withVirtualTime(() -> flujoIntervalo, () -> virtualTimeScheduler)
    .thenAwait(Duration.ofSeconds(10))
    .expectNext(0L, 1L)
    .verifyComplete();

El TestPublisher es una herramienta flexible para crear publishers controlables durante las pruebas. Con TestPublisher, es posible emitir señales manualmente, ya sea datos, errores o eventos de completitud, facilitando la simulación de diferentes condiciones en los flujos.

import reactor.test.publisher.TestPublisher;

TestPublisher<String> testPublisher = TestPublisher.create();

Flux<String> flujoPrueba = testPublisher.flux();

StepVerifier.create(flujoPrueba)
    .then(() -> testPublisher
        .next("Dato1", "Dato2")
        .error(new RuntimeException("Error simulado")))
    .expectNext("Dato1", "Dato2")
    .expectErrorMatches(throwable -> 
        throwable instanceof RuntimeException && 
        throwable.getMessage().equals("Error simulado"))
    .verify();

Además, reactor-test ofrece herramientas como Scannable para inspeccionar internamente los operadores de un flujo, y utilidades para trabajar con Context y verificar la propagación de datos contextuales a través de los flujos reactivos.

Otra característica útil es la capacidad de utilizar Hooks para interceptar y modificar el comportamiento global de los flujos durante las pruebas. Con los Hooks, es posible inyectar comportamientos personalizados o captar métricas detalladas de ejecución.

Hooks.onOperatorDebug();

Mono<String> flujoConError = Mono.error(new IllegalArgumentException("Argumento no válido"));

StepVerifier.create(flujoConError)
    .verifyErrorMessage("Argumento no válido");

Todas estas herramientas se integran de forma natural con frameworks de pruebas como JUnit 5, permitiendo escribir pruebas claras y expresivas para aplicaciones reactivas. Al aprovechar las utilidades de reactor-test, los desarrolladores pueden garantizar que sus aplicaciones reactivas respondan correctamente bajo diversas condiciones y manejos de datos.

Testing de operadores y flujos complejos con StepVerifier, operador test

Cuando trabajamos con aplicaciones reactivas en Spring WebFlux, es fundamental asegurar que los flujos complejos y los operadores se comporten según lo esperado. El uso de StepVerifier nos permite crear pruebas detalladas y precisas para validar estos flujos.

El StepVerifier es una herramienta que facilita la verificación paso a paso de las emisiones y eventos en flujos Mono y Flux. Al enfrentarnos a flujos que incorporan operadores como flatMap, zip, merge o concat, es esencial comprobar que la combinación y transformación de datos se realiza correctamente.

Consideremos un ejemplo donde utilizamos el operador flatMap para transformar y combinar datos de múltiples fuentes:

Flux<String> nombres = Flux.just("Ana", "Juan", "Pedro");
Flux<Integer> edades = Flux.just(25, 30, 35);

Flux<String> combinacion = nombres
    .flatMap(nombre -> edades
        .map(edad -> nombre + " tiene " + edad + " años")
    );

StepVerifier.create(combinacion)
    .expectNext("Ana tiene 25 años", "Ana tiene 30 años", "Ana tiene 35 años",
                "Juan tiene 25 años", "Juan tiene 30 años", "Juan tiene 35 años",
                "Pedro tiene 25 años", "Pedro tiene 30 años", "Pedro tiene 35 años")
    .verifyComplete();

En este ejemplo, el uso de expectNext nos permite especificar de manera precisa la secuencia esperada de valores. Es importante notar que, debido a la naturaleza combinatoria de flatMap, el orden de emisión puede variar. Para manejar casos donde el orden no es determinante, podemos utilizar expectNextMatches o recordWith junto con consumeRecordedWith para validar los elementos de forma más flexible.

Cuando trabajamos con operadores temporales como delayElements o interval, la gestión del tiempo en las pruebas se vuelve crucial. Aquí es donde el Operador de Prueba y el VirtualTimeScheduler son de gran utilidad. El VirtualTimeScheduler nos permite simular el paso del tiempo sin demoras reales en la ejecución de las pruebas.

import reactor.test.scheduler.VirtualTimeScheduler;

VirtualTimeScheduler.getOrSet();

Flux<Long> flujoIntervalo = Flux.interval(Duration.ofSeconds(5))
    .take(3);

StepVerifier.withVirtualTime(() -> flujoIntervalo)
    .expectSubscription()
    .thenAwait(Duration.ofSeconds(15))
    .expectNext(0L, 1L, 2L)
    .verifyComplete();

En este caso, utilizamos withVirtualTime para indicar que el flujo debe ejecutarse en un contexto de tiempo virtual. Al thenAwait por la duración necesaria, podemos avanzar el tiempo y validar las emisiones esperadas rápidamente.

Para flujos que pueden generar errores, es fundamental probar tanto el camino exitoso como el manejo de excepciones. Supongamos un flujo que procesa una lista de números y arroja una excepción si encuentra un valor negativo:

Flux<Integer> numeros = Flux.just(1, 2, -3, 4);

Flux<Integer> procesados = numeros.map(numero -> {
    if (numero < 0) {
        throw new IllegalArgumentException("Número negativo no permitido");
    }
    return numero * 2;
});

StepVerifier.create(procesados)
    .expectNext(2, 4)
    .expectErrorMatches(error -> error instanceof IllegalArgumentException &&
                                 error.getMessage().equals("Número negativo no permitido"))
    .verify();

Aquí, expectErrorMatches nos permite validar que el flujo efectivamente arroja la excepción esperada con el mensaje correcto, asegurando un manejo adecuado de errores.

Cuando los flujos son más complejos y combinan múltiples operadores, es posible que necesitemos utilizar StepVerifier.Assertions para realizar verificaciones más detalladas. Por ejemplo, al trabajar con flujos anidados o encadenados, podemos usar assertNext para inspeccionar cada elemento individualmente:

Flux<String> datos = Flux.just("A", "B", "C")
    .concatWith(Flux.error(new RuntimeException("Error simulado")))
    .onErrorReturn("D");

StepVerifier.create(datos)
    .expectNext("A")
    .expectNext("B")
    .expectNext("C")
    .expectNext("D")
    .verifyComplete();

En este flujo, tras la ocurrencia de un error, utilizamos onErrorReturn para emitir un valor predeterminado. Con StepVerifier, podemos verificar que el flujo emite los valores adecuados incluso después de manejar un error.

En situaciones donde necesitamos comprobar aspectos más sutiles del flujo, como las solicitudes de demanda (backpressure), StepVerifier ofrece métodos como expectSubscription y thenRequest. Esto es útil para validar cómo el flujo responde a diferentes patrones de demanda:

Flux<Integer> numeros = Flux.range(1, 10);

StepVerifier.create(numeros, StepVerifierOptions.create().initialRequest(0))
    .expectSubscription()
    .thenRequest(5)
    .expectNext(1, 2, 3, 4, 5)
    .thenRequest(5)
    .expectNext(6, 7, 8, 9, 10)
    .verifyComplete();

Mediante el control explícito de las solicitudes, podemos simular consumidores lentos y verificar que el flujo se comporta correctamente bajo presión de demanda.

Es posible que necesitemos probar flujos que incluyan suscripciones condicionales o cancelaciones. Para estos casos, StepVerifier permite verificar eventos como la suscripción y cancelación mediante thenCancel y expectNoEvent:

Flux<Long> infinito = Flux.interval(Duration.ofMillis(100));

StepVerifier.create(infinito)
    .expectSubscription()
    .expectNext(0L, 1L, 2L)
    .thenCancel()
    .verify();

Con este enfoque, podemos asegurar que nuestros flujos reaccionan adecuadamente a las cancelaciones y no consumen recursos innecesarios.

El uso avanzado de StepVerifier es esencial para probar operadores y flujos complejos en Spring WebFlux. Al comprender y aplicar estas técnicas, garantizamos que nuestras aplicaciones reactivas funcionen de manera fiable y eficiente.

Validación de secuencias Mono y Flux

La validación de secuencias Mono y Flux es esencial para asegurar que las aplicaciones reactivas en Spring WebFlux se comporten según lo esperado. Al realizar pruebas unitarias de estas secuencias, es importante verificar no solo los datos emitidos, sino también cómo se gestionan los errores, las cancelaciones y las suscripciones.

Para validar un Mono, que representa una secuencia que puede emitir cero o un elemento, podemos utilizar el StepVerifier para comprobar el valor emitido o verificar que no se emite ningún valor. Además, es fundamental validar cómo se manejan las señales de éxito y error en la secuencia.

Por ejemplo, para probar un Mono que debería emitir un valor específico:

@Test
void testMonoConValor() {
    Mono<String> mono = Mono.just("Hola Mundo");

    StepVerifier.create(mono)
        .expectNext("Hola Mundo")
        .verifyComplete();
}

En este caso, el StepVerifier verifica que el Mono emite el valor "Hola Mundo" y luego completa correctamente.

Si queremos probar un Mono que no emite ningún valor pero completa sin errores:

@Test
void testMonoVacio() {
    Mono<String> monoVacio = Mono.empty();

    StepVerifier.create(monoVacio)
        .verifyComplete();
}

Aquí, validamos que el Mono está vacío y completa satisfactoriamente.

Para el caso donde el Mono emite un error, es crucial verificar el tipo de excepción y el mensaje asociado:

@Test
void testMonoConError() {
    Mono<String> monoError = Mono.error(new IllegalStateException("Estado no permitido"));

    StepVerifier.create(monoError)
        .expectErrorMatches(throwable ->
            throwable instanceof IllegalStateException &&
            throwable.getMessage().equals("Estado no permitido"))
        .verify();
}

Con este enfoque, confirmamos que el Mono lanza una IllegalStateException con el mensaje esperado.

Al validar secuencias Flux, que pueden emitir múltiples elementos, es importante comprobar la secuencia completa de valores, así como el manejo de errores y la correcta finalización de la secuencia.

Por ejemplo, para probar un Flux que emite una serie de números:

@Test
void testFluxDeNumeros() {
    Flux<Integer> numeros = Flux.range(1, 5);

    StepVerifier.create(numeros)
        .expectNext(1, 2, 3, 4, 5)
        .verifyComplete();
}

En este test, aseguramos que el Flux emite los números del 1 al 5 y luego completa sin errores.

Si la secuencia incluye una mezcla de valores y errores, podemos validarla de la siguiente manera:

@Test
void testFluxConError() {
    Flux<String> datos = Flux.just("A", "B", "C")
        .concatWith(Mono.error(new RuntimeException("Error de prueba")));

    StepVerifier.create(datos)
        .expectNext("A")
        .expectNext("B")
        .expectNext("C")
        .expectErrorMessage("Error de prueba")
        .verify();
}

Aquí, verificamos que el Flux emite los valores "A", "B", "C" y luego termina con un error que contiene el mensaje "Error de prueba".

Para secuencias que pueden tardar cierto tiempo en emitir valores, como aquellas que utilizan operadores temporales, debemos manejar la validación del tiempo adecuadamente. El uso de StepVerifier junto con VirtualTimeScheduler permite simular el paso del tiempo en las pruebas:

@Test
void testFluxConRetardo() {
    VirtualTimeScheduler.getOrSet();
    Flux<Long> intervalo = Flux.interval(Duration.ofSeconds(1)).take(3);

    StepVerifier.withVirtualTime(() -> intervalo)
        .expectSubscription()
        .thenAwait(Duration.ofSeconds(3))
        .expectNext(0L, 1L, 2L)
        .verifyComplete();
}

En este ejemplo, utilizamos el VirtualTimeScheduler para avanzar el tiempo virtualmente y verificar que el Flux emite los valores esperados sin tener que esperar realmente tres segundos.

Es común también validar transformaciones en las secuencias, asegurando que los operadores aplicados producen los resultados deseados. Por ejemplo, podemos probar un Flux que transforma cadenas a mayúsculas:

@Test
void testFluxTransformado() {
    Flux<String> flux = Flux.just("spring", "boot", "reactivo")
        .map(String::toUpperCase);

    StepVerifier.create(flux)
        .expectNext("SPRING", "BOOT", "REACTIVO")
        .verifyComplete();
}

Aquí, validamos que la transformación a mayúsculas se realiza correctamente en cada elemento del Flux.

Cuando las secuencias son condicionales o dependen de ciertos criterios, podemos utilizar expectNextMatches para validar elementos que satisfacen una condición específica:

@Test
void testFluxConFiltro() {
    Flux<Integer> numeros = Flux.range(1, 10)
        .filter(n -> n % 2 == 0);

    StepVerifier.create(numeros)
        .expectNextMatches(n -> n == 2)
        .expectNextMatches(n -> n == 4)
        .expectNextMatches(n -> n == 6)
        .expectNextMatches(n -> n == 8)
        .expectNextMatches(n -> n == 10)
        .verifyComplete();
}

En este caso, confirmamos que el Flux filtra correctamente los números pares del rango dado.

La validación del comportamiento bajo presión de demanda (backpressure) es otra consideración clave, especialmente en flujos donde el consumidor es más lento que el productor. Podemos simular y verificar este escenario en nuestras pruebas:

@Test
void testBackpressure() {
    Flux<Integer> numeros = Flux.range(1, 10);

    StepVerifier.create(numeros, StepVerifierOptions.create().initialRequest(2))
        .expectNext(1, 2)
        .thenRequest(2)
        .expectNext(3, 4)
        .thenRequest(6)
        .expectNext(5, 6, 7, 8, 9, 10)
        .verifyComplete();
}

Este test muestra cómo el Flux responde a las solicitudes de demanda, emitiendo solo los elementos solicitados en cada momento.

Es importante también validar los flujos que utilizan contextos reactivos, los cuales permiten pasar información a través de la secuencia sin modificar los datos emitidos. Podemos verificar que el contexto se propaga correctamente:

@Test
void testFluxConContexto() {
    Mono<String> saludo = Mono.deferContextual(ctx ->
        Mono.just("Hola " + ctx.get("usuario"))
    );

    StepVerifier.create(saludo.contextWrite(Context.of("usuario", "Carlos")))
        .expectNext("Hola Carlos")
        .verifyComplete();
}

En este ejemplo, verificamos que el Contexto proporciona el valor adecuado al operador y que el Mono resultante emite el saludo correcto.

Al probar secuencias que involucran suscripciones y cancelaciones, podemos utilizar métodos como thenCancel para asegurar que el flujo se comporta adecuadamente ante una cancelación:

@Test
void testFluxCancelado() {
    Flux<Long> infinito = Flux.interval(Duration.ofMillis(100));

    StepVerifier.create(infinito)
        .expectNextCount(5)
        .thenCancel()
        .verify();
}

Este test confirma que después de emitir cinco elementos, el Flux puede ser cancelado sin problemas.

Finalmente, cuando trabajamos con flujos combinados o anidados, es esencial validar cómo interactúan entre sí. Por ejemplo, al combinar dos Flux con el operador zip:

@Test
void testFluxCombinado() {
    Flux<String> nombres = Flux.just("Ana", "Luis", "María");
    Flux<Integer> edades = Flux.just(30, 25, 28);

    Flux<String> combinados = Flux.zip(nombres, edades, (nombre, edad) -> nombre + " tiene " + edad + " años");

    StepVerifier.create(combinados)
        .expectNext("Ana tiene 30 años")
        .expectNext("Luis tiene 25 años")
        .expectNext("María tiene 28 años")
        .verifyComplete();
}

Con este test, aseguramos que el Flux resultante de la combinación emite los valores esperados en el orden correcto.

Uso de WebTestClient

El WebTestClient es una herramienta esencial para realizar pruebas integradas en aplicaciones reactivas desarrolladas con Spring WebFlux. Permite simular peticiones HTTP y verificar tanto las respuestas como el comportamiento de los controladores de forma asíncrona y no bloqueante.

Para comenzar a utilizar WebTestClient, es necesario configurarlo en nuestras pruebas. Una forma común es crear una instancia vinculada al servidor WebFlux de la aplicación:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ControladorReactivoTest {

    @Autowired
    private WebTestClient webTestClient;

    // Pruebas aquí
}

En este ejemplo, con la anotación @SpringBootTest, levantamos el contexto de la aplicación en un puerto aleatorio y autoinyectamos el WebTestClient. De este modo, podemos realizar pruebas que interactúan con el servidor real, abarcando todo el flujo desde la recepción de la petición hasta la generación de la respuesta.

Si preferimos probar solo el controlador específico sin levantar todo el contexto de la aplicación, podemos utilizar @WebFluxTest:

@WebFluxTest(ControladorReactivo.class)
public class ControladorReactivoTest {

    @Autowired
    private WebTestClient webTestClient;

    // Pruebas aquí
}

Aquí, @WebFluxTest carga únicamente los componentes necesarios para probar el controlador ControladorReactivo.

A continuación, veamos cómo utilizar WebTestClient para realizar una petición GET y validar la respuesta:

@Test
void testObtenerElemento() {
    webTestClient.get()
        .uri("/api/elementos/1")
        .accept(MediaType.APPLICATION_JSON)
        .exchange()
        .expectStatus().isOk()
        .expectHeader().contentType(MediaType.APPLICATION_JSON)
        .expectBody(Elemento.class)
        .value(elemento -> {
            assertEquals(1L, elemento.getId());
            assertEquals("Elemento 1", elemento.getNombre());
        });
}

En esta prueba, realizamos una solicitud GET a /api/elementos/1, indicamos que aceptamos JSON y luego intercambiamos la solicitud. Validamos que el estatus HTTP es 200 OK, que el header Content-Type es application/json y que el cuerpo de la respuesta contiene un objeto Elemento con los valores esperados.

Para probar una solicitud POST, podemos enviar un objeto en el cuerpo de la petición y verificar la respuesta:

@Test
void testCrearElemento() {
    Elemento nuevoElemento = new Elemento(null, "Nuevo Elemento");

    webTestClient.post()
        .uri("/api/elementos")
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON)
        .bodyValue(nuevoElemento)
        .exchange()
        .expectStatus().isCreated()
        .expectHeader().exists("Location")
        .expectBody(Elemento.class)
        .value(elemento -> {
            assertNotNull(elemento.getId());
            assertEquals("Nuevo Elemento", elemento.getNombre());
        });
}

Aquí, enviamos un Elemento sin ID, indicando que es un nuevo recurso. Validamos que el estatus es 201 Created, que existe el header Location y que el cuerpo de la respuesta contiene el elemento creado con un ID asignado.

El WebTestClient también permite probar flujos reactivos como los que retornan un Flux. Por ejemplo, para probar un endpoint que devuelve una lista de elementos:

@Test
void testObtenerTodosLosElementos() {
    webTestClient.get()
        .uri("/api/elementos")
        .accept(MediaType.APPLICATION_JSON)
        .exchange()
        .expectStatus().isOk()
        .expectHeader().contentType(MediaType.APPLICATION_NDJSON)
        .expectBodyList(Elemento.class)
        .value(elementos -> {
            assertFalse(elementos.isEmpty());
            assertTrue(elementos.size() >= 1);
        });
}

En este caso, verificamos que el contenido es de tipo application/x-ndjson, propio de flujos Flux serializados en JSON. Validamos que la lista de elementos no esté vacía y que contenga al menos un elemento.

Para pruebas más avanzadas, podemos utilizar el método returnResult() para acceder al flujo reactivo y realizar validaciones más detalladas:

@Test
void testObtenerFlujoDeElementos() {
    Flux<Elemento> flujoElementos = webTestClient.get()
        .uri("/api/elementos/stream")
        .accept(MediaType.APPLICATION_STREAM_JSON)
        .exchange()
        .expectStatus().isOk()
        .returnResult(Elemento.class)
        .getResponseBody();

    StepVerifier.create(flujoElementos)
        .expectNextMatches(elemento -> elemento.getNombre().startsWith("Elemento"))
        .expectNextCount(4)
        .thenCancel()
        .verify();
}

En este ejemplo, obtenemos el Flux de elementos y utilizamos StepVerifier para validar el flujo. Comprobamos que el primer elemento cumple una condición específica y luego esperamos cuatro elementos más antes de cancelar el flujo.

El WebTestClient también es útil para probar manejo de errores en los endpoints. Si un endpoint retorna un error, podemos validar el estatus y el cuerpo de la respuesta:

@Test
void testElementoNoEncontrado() {
    webTestClient.get()
        .uri("/api/elementos/999")
        .accept(MediaType.APPLICATION_JSON)
        .exchange()
        .expectStatus().isNotFound()
        .expectBody()
        .isEmpty();
}

Aquí, intentamos obtener un elemento con un ID inexistente y verificamos que el estatus es 404 Not Found y que el cuerpo de la respuesta está vacío.

Para probar endpoints protegidos con Spring Security, podemos configurar el WebTestClient para incluir autenticación. Por ejemplo:

@Test
void testAccesoNoAutorizado() {
    webTestClient.get()
        .uri("/api/elementos/1")
        .exchange()
        .expectStatus().isUnauthorized();
}

@Test
void testAccesoAutorizado() {
    webTestClient.mutate().filter(basicAuthentication("user", "password")).build()
        .get()
        .uri("/api/elementos/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Elemento.class)
        .value(elemento -> assertEquals("Elemento 1", elemento.getNombre()));
}

En la primera prueba, verificamos que sin credenciales, el acceso está no autorizado. En la segunda, añadimos autenticación básica y comprobamos que podemos acceder al recurso.

Además, el WebTestClient permite validar los cookies y las sesiones que se mantienen entre las peticiones:

@Test
void testManejoDeCookies() {
    webTestClient.get()
        .uri("/api/iniciar-sesion")
        .exchange()
        .expectStatus().isOk()
        .expectCookie().exists("SESSIONID")
        .expectBody()
        .isEmpty();
}

Aquí, después de iniciar sesión, verificamos que se ha establecido una cookie de sesión llamada SESSIONID.

Cuando los endpoints retornan contenido binario o tipos de medios personalizados, podemos ajustar el WebTestClient para manejarlos adecuadamente:

@Test
void testDescargaDeArchivo() {
    webTestClient.get()
        .uri("/api/descargar/archivo")
        .accept(MediaType.APPLICATION_OCTET_STREAM)
        .exchange()
        .expectStatus().isOk()
        .expectHeader().contentType(MediaType.APPLICATION_OCTET_STREAM)
        .expectBody(byte[].class)
        .value(bytes -> assertTrue(bytes.length > 0));
}

En este caso, descargamos un archivo y verificamos que el cuerpo de la respuesta contiene datos.

El uso de WebTestClient también es aplicable para probar websockets y otros protocolos basados en HTTP. Sin embargo, en el contexto de REST APIs y aplicaciones WebFlux, su principal utilidad es permitir pruebas reactivas y no bloqueantes que reflejan el comportamiento real de la aplicación.

Es importante destacar que podemos personalizar el WebTestClient para añadir filtros, codificadores y otras configuraciones necesarias para nuestras pruebas:

WebTestClient clientPersonalizado = WebTestClient.bindToServer()
    .baseUrl("http://localhost:8080")
    .filter(logRequest())
    .filter(logResponse())
    .build();

Con este WebTestClient personalizado, añadimos filtros para registrar las solicitudes y respuestas, lo que facilita la depuración de las pruebas.

Finalmente, para pruebas que requieren modificar el contexto o inyectar dependencias, podemos utilizar @MockBean y otras herramientas de Spring Boot Test para simular comportamientos:

@WebFluxTest(ControladorReactivo.class)
public class ControladorReactivoTest {

    @Autowired
    private WebTestClient webTestClient;

    @MockBean
    private ServicioReactivo servicioReactivo;

    @Test
    void testServicioMockeado() {
        when(servicioReactivo.obtenerElementoPorId(1L))
            .thenReturn(Mono.just(new Elemento(1L, "Elemento Mockeado")));

        webTestClient.get()
            .uri("/api/elementos/1")
            .exchange()
            .expectStatus().isOk()
            .expectBody(Elemento.class)
            .value(elemento -> assertEquals("Elemento Mockeado", elemento.getNombre()));
    }
}

En este ejemplo, mockeamos el servicio para controlar la respuesta y verificar que el controlador gestiona correctamente el elemento proporcionado por el servicio simulado.

Aprende SpringBoot GRATIS online

Ejercicios de esta lección Testing de aplicaciones reactivas WebFlux

Evalúa tus conocimientos de esta lección Testing de aplicaciones reactivas WebFlux con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

API Query By Example (QBE)

Spring Boot
Test

Identificadores y relaciones JPA

Spring Boot
Puzzle

Borrar datos de base de datos

Spring Boot
Test

Web y Test Starters

Spring Boot
Puzzle

Métodos find en repositorios

Spring Boot
Test

Controladores Spring MVC

Spring Boot
Código

Inserción de datos

Spring Boot
Test

CRUD Customers Spring MVC + Spring Data JPA

Spring Boot
Proyecto

Backend API REST con Spring Boot

Spring Boot
Proyecto

Controladores Spring REST

Spring Boot
Código

Uso de Spring con Thymeleaf

Spring Boot
Puzzle

API Specification

Spring Boot
Puzzle

Registro de usuarios

Spring Boot
Test

Crear entidades JPA

Spring Boot
Código

Asociaciones en JPA

Spring Boot
Test

Asociaciones de entidades JPA

Spring Boot
Código

Integración con Vue

Spring Boot
Test

Consultas JPQL

Spring Boot
Código

Open API y cómo agregarlo en Spring Boot

Spring Boot
Puzzle

Uso de Controladores REST

Spring Boot
Puzzle

Repositorios reactivos

Spring Boot
Test

Inyección de dependencias

Spring Boot
Test

Introducción a Spring Boot

Spring Boot
Test

CRUD y JPA Repository

Spring Boot
Puzzle

Inyección de dependencias

Spring Boot
Código

Vista en Spring MVC con Thymeleaf

Spring Boot
Test

Servicios en Spring

Spring Boot
Código

Operadores Reactivos

Spring Boot
Puzzle

Configuración de Vue

Spring Boot
Puzzle

Entidades JPA

Spring Boot
Test

Integración con Angular

Spring Boot
Test

API Specification

Spring Boot
Test

API Query By Example (QBE)

Spring Boot
Puzzle

Controladores MVC

Spring Boot
Test

Anotaciones y mapeo en JPA

Spring Boot
Puzzle

Consultas JPQL con @Query en Spring Data JPA

Spring Boot
Test

Repositorios Spring Data

Spring Boot
Test

Inyección de dependencias

Spring Boot
Puzzle

Data JPA y Mail Starters

Spring Boot
Test

Configuración de Angular

Spring Boot
Puzzle

Controladores Spring REST

Spring Boot
Test

Configuración de Controladores MVC

Spring Boot
Puzzle

Consultas JPQL con @Query en Spring Data JPA

Spring Boot
Puzzle

Actualizar datos de base de datos

Spring Boot
Test

Verificar token JWT en peticiones

Spring Boot
Test

Login de usuarios

Spring Boot
Test

Integración con React

Spring Boot
Test

Configuración de React

Spring Boot
Puzzle

Todas las lecciones de SpringBoot

Accede a todas las lecciones de SpringBoot y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Spring Boot

Spring Boot

Introducción Y Entorno

Spring Boot Starters

Spring Boot

Introducción Y Entorno

Inyección De Dependencias

Spring Boot

Introducción Y Entorno

Controladores Spring Mvc

Spring Boot

Spring Web

Vista En Spring Mvc Con Thymeleaf

Spring Boot

Spring Web

Controladores Spring Rest

Spring Boot

Spring Web

Open Api Y Cómo Agregarlo En Spring Boot

Spring Boot

Spring Web

Servicios En Spring

Spring Boot

Spring Web

Clientes Resttemplate Y Restclient

Spring Boot

Spring Web

Rxjava En Spring Web

Spring Boot

Spring Web

Crear Entidades Jpa

Spring Boot

Persistencia Spring Data

Asociaciones De Entidades Jpa

Spring Boot

Persistencia Spring Data

Repositorios Spring Data

Spring Boot

Persistencia Spring Data

Métodos Find En Repositorios

Spring Boot

Persistencia Spring Data

Inserción De Datos

Spring Boot

Persistencia Spring Data

Actualizar Datos De Base De Datos

Spring Boot

Persistencia Spring Data

Borrar Datos De Base De Datos

Spring Boot

Persistencia Spring Data

Consultas Jpql Con @Query En Spring Data Jpa

Spring Boot

Persistencia Spring Data

Api Query By Example (Qbe)

Spring Boot

Persistencia Spring Data

Api Specification

Spring Boot

Persistencia Spring Data

Repositorios Reactivos

Spring Boot

Persistencia Spring Data

Introducción E Instalación De Apache Kafka

Spring Boot

Mensajería Asíncrona

Crear Proyecto Con Apache Kafka

Spring Boot

Mensajería Asíncrona

Creación De Producers

Spring Boot

Mensajería Asíncrona

Creación De Consumers

Spring Boot

Mensajería Asíncrona

Kafka Streams En Spring Boot

Spring Boot

Mensajería Asíncrona

Introducción A Spring Webflux

Spring Boot

Reactividad Webflux

Spring Data R2dbc

Spring Boot

Reactividad Webflux

Controlador Rest Reactivo Basado En Anotaciones

Spring Boot

Reactividad Webflux

Controlador Rest Reactivo Funcional

Spring Boot

Reactividad Webflux

Operadores Reactivos Básicos

Spring Boot

Reactividad Webflux

Operadores Reactivos Avanzados

Spring Boot

Reactividad Webflux

Cliente Reactivo Webclient

Spring Boot

Reactividad Webflux

Introducción A Spring Security

Spring Boot

Seguridad Con Spring Security

Seguridad Basada En Formulario En Mvc Con Thymeleaf

Spring Boot

Seguridad Con Spring Security

Registro De Usuarios

Spring Boot

Seguridad Con Spring Security

Login De Usuarios

Spring Boot

Seguridad Con Spring Security

Verificar Token Jwt En Peticiones

Spring Boot

Seguridad Con Spring Security

Seguridad Jwt En Api Rest Spring Web

Spring Boot

Seguridad Con Spring Security

Seguridad Jwt En Api Rest Reactiva Spring Webflux

Spring Boot

Seguridad Con Spring Security

Autenticación Y Autorización Con Anotaciones

Spring Boot

Seguridad Con Spring Security

Testing Unitario De Componentes Y Servicios

Spring Boot

Testing Con Spring Test

Testing De Repositorios Spring Data Jpa

Spring Boot

Testing Con Spring Test

Testing Controladores Spring Mvc Con Thymeleaf

Spring Boot

Testing Con Spring Test

Testing Controladores Rest Con Json

Spring Boot

Testing Con Spring Test

Testing De Aplicaciones Reactivas Webflux

Spring Boot

Testing Con Spring Test

Testing De Seguridad Spring Security

Spring Boot

Testing Con Spring Test

Testing Con Apache Kafka

Spring Boot

Testing Con Spring Test

Integración Con Angular

Spring Boot

Integración Frontend

Integración Con React

Spring Boot

Integración Frontend

Integración Con Vue

Spring Boot

Integración Frontend

Accede GRATIS a SpringBoot y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Aprender a ejecutar pruebas de controladores reactivos
  • Uso de WebTestClient
  • Uso de StepVerifier