Día 21 de 30 Fase 5: Testing de Integración, APIs y Carga

Tests de API y de Carga: MockMvc, REST Assured, BDD y JMeter

Probar la API como la usa un cliente, escribir escenarios de negocio con Cucumber y medir el rendimiento antes de producción

Para dominar. Al terminar deberías poder hacerlo sin ayuda.

  • 4h de estudio
  • 5 conceptos
  • 3 preguntas
  • 2 katas
Java 17 LTSJava 21 LTSMockMvcREST AssuredCucumberJMeter
Al terminar este día sabrás 4
  • Probar el contrato HTTP de un endpoint con @WebMvcTest y MockMvc.
  • Escribir un test de recorrido completo con REST Assured y Testcontainers.
  • Expresar una regla de negocio como escenario de Cucumber en español.
  • Diseñar y leer una prueba de carga con JMeter fijándote en percentiles y errores.

MockMvc y @WebMvcTest a Fondo

@WebMvcTest arranca solo la capa web y MockMvc simula peticiones HTTP sin servidor real: comprueba rutas, validación, códigos de estado, cabeceras y el JSON de respuesta con jsonPath. Los servicios se sustituyen con @MockitoBean.

En una línea mvc.perform(post("/tareas").contentType(APPLICATION_JSON).content("{}")).andExpect(status().isBadRequest()).andExpect(jsonPath("$.title").exists());

Contrato de POST /tareas java
@WebMvcTest(TareaController.class)
class TareaControllerTest {

    @Autowired MockMvc mvc;
    @MockitoBean TareaService servicio;

    @Test
    void crear_devuelve_201_con_location() throws Exception {
        given(servicio.crear(any())).willReturn(new TareaDto(42L, "Revisar PR", Estado.PENDIENTE));

        mvc.perform(post("/tareas")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"titulo": "Revisar PR"}
                    """))
            .andExpect(status().isCreated())
            .andExpect(header().string("Location", endsWith("/tareas/42")))
            .andExpect(jsonPath("$.estado").value("PENDIENTE"));
    }

    @Test
    void crear_sin_titulo_devuelve_400_con_problem_detail() throws Exception {
        mvc.perform(post("/tareas")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{}"))
            .andExpect(status().isBadRequest())
            .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
            .andExpect(jsonPath("$.status").value(400));
        verifyNoInteractions(servicio);
    }
}
Cómo funciona por dentro

@WebMvcTest(TareaController.class) crea un contexto de Spring reducido: el controlador indicado, los @ControllerAdvice, los convertidores de Jackson, la validación, los filtros y la seguridad web si está configurada. No crea servicios ni repositorios, así que los que necesite el controlador se aportan con @MockitoBean. Arranca en uno o dos segundos y cada test tarda milisegundos.

MockMvc ejecuta la petición por toda la pila de Spring MVC sin abrir un puerto: el DispatcherServlet resuelve la ruta, deserializa el cuerpo, valida, llama al controlador y serializa la respuesta. Con jsonPath compruebas campos concretos del JSON, y con header(), las cabeceras. Desde Spring Framework 6.2 existe también MockMvcTester, con aserciones al estilo AssertJ.

Qué probar aquí: códigos de estado, validación de la entrada, formato de los errores (ProblemDetail), cabeceras como Location, la serialización de fechas y enums, y las reglas de seguridad de cada endpoint. Qué no: la lógica de negocio (va en tests unitarios del servicio) ni el acceso a datos.

Prueba en milisegundos el contrato HTTP de cada endpoint: que un DTO inválido devuelve 400 con ProblemDetail y que crear devuelve 201 con Location.

Se conecta con @MockitoBeanProblemDetailBean Validation

Cuatro bloques cronometrados. No pases al siguiente sin tener el resultado esperado.

  1. Bloque 1: Contrato HTTP con MockMvc (0 - 25 min)

    Qué estudiar. @WebMvcTest, MockMvc, jsonPath y @MockitoBean.

    Pregunta a responder. ¿Qué debe devolver tu API ante un DTO inválido?

    Resultado esperado. Tests de MockMvc para crear una tarea: 201 con Location y 400 con ProblemDetail.

  2. Bloque 2: Recorrido Completo con REST Assured (25 - 50 min)

    Qué estudiar. @SpringBootTest(webEnvironment = RANDOM_PORT) con Testcontainers.

    Pregunta a responder. ¿Qué detecta este test que MockMvc no puede ver?

    Resultado esperado. Test que crea, consulta y completa una tarea a través de HTTP real.

  3. Bloque 3: BDD y Carga (50 - 80 min)

    Qué estudiar. Un .feature en español con Cucumber y un plan de JMeter en modo no interactivo.

    Pregunta a responder. ¿Qué percentil y qué tasa de errores vas a exigir?

    Resultado esperado. Un escenario de Cucumber de negocio y un informe HTML de JMeter con 100 usuarios durante 2 minutos.

  4. Cierre de Consolidación (80 - 90 min)

    Qué estudiar. Repaso de la rúbrica del tutor.

    Pregunta a responder. Explicar qué tipo de test usarías para validación, reglas de negocio, flujo completo y rendimiento.

    Resultado esperado. Propuesta de pipeline: qué se ejecuta en cada push, en cada pull request y antes de cada release.

Tres formas de comprobar si lo has entendido, de menos a más exigente: explicarlo con tus palabras, responder preguntas trampa y resolver el reto del tutor.

Explícalo desde cero

Lee cada ficha y explícala en voz alta antes de seguir: si puedes contárselo a alguien que empieza, lo has entendido.

MockMvc frente a REST Assured

Lo que debes entender

MockMvc llama a tus controladores sin abrir un puerto: es rápido y prueba la capa web. REST Assured habla HTTP de verdad con la aplicación entera arrancada: es lento y prueba todo el recorrido. Se usan los dos, cada uno para lo suyo.

Analogía para contarlo

Probar el volante de un coche en el taller (MockMvc) frente a dar una vuelta por la ciudad (REST Assured).

Lo que suele explicarse mal

Decir que MockMvc «no es un test real». Es real para lo que prueba: el contrato HTTP de los controladores.

Señal de que aún no lo dominas

Si todos tus tests de API arrancan la aplicación completa y la suite tarda más de diez minutos.

Preguntas trampa

Responde de memoria, en voz alta o por escrito, y después abre la respuesta razonada: lo importante es el porqué, no la palabra exacta.

Nivel 1 (Básico) ¿Qué carga @WebMvcTest y qué deja fuera? Ver respuesta

Respuesta razonada

Carga la parte web de Spring MVC: el controlador indicado (o todos si no indicas ninguno), los @ControllerAdvice, los @JsonComponent, los convertidores, los filtros, los WebMvcConfigurer y la configuración de Spring Security para la web. Deja fuera los @Service, @Repository y @Component normales, el DataSource y JPA.

Por eso cualquier dependencia del controlador debe aportarse con @MockitoBean o con @Import de una configuración concreta; si no, el contexto no arranca por falta del bean. Esa restricción es justo lo que lo hace rápido y centrado en el contrato HTTP.

Qué está evaluando la pregunta

Carga los controladores, los @ControllerAdvice, los filtros, la configuración de Jackson y la validación. Deja fuera servicios, repositorios y base de datos, que se sustituyen con @MockitoBean. Por eso es rápido.

Nivel 2 (Intermedio) ¿Por qué la media del tiempo de respuesta es un mal indicador en una prueba de carga? Ver respuesta

Respuesta razonada

Porque la media mezcla todas las peticiones y se deja dominar por la mayoría rápida. Si 95 de cada 100 peticiones tardan 50 ms y 5 tardan 4 segundos, la media ronda los 250 ms, un número que no describe a nadie: ni a los usuarios contentos ni a los que esperan 4 segundos.

Los percentiles dicen qué experimenta cada franja: el p95 es el tiempo por debajo del cual quedan el 95 % de las peticiones. Con varias llamadas por pantalla, casi todos los usuarios sufren alguna vez el p99, por eso los objetivos de servicio se expresan en percentiles y se vigilan junto con la tasa de errores.

Qué está evaluando la pregunta

Porque unas pocas peticiones muy lentas quedan escondidas entre muchas rápidas. El percentil 95 o 99 dice qué tiempo sufre el 5 % o el 1 % de usuarios con peor experiencia, que es donde aparecen los problemas de colas, locks o pools agotados.

Nivel 3 (Entrevista Senior) En la prueba de carga, a partir de 150 usuarios el tiempo de respuesta se dispara, pero la CPU está al 30 %. ¿Dónde buscarías? Ver respuesta

Respuesta razonada

Si la latencia se dispara con la CPU baja, los hilos están esperando, no trabajando. El sospechoso habitual es el pool de conexiones a la base de datos: HikariCP trae 10 conexiones por defecto, y con 150 usuarios concurrentes las peticiones hacen cola para conseguir una. Otros candidatos: el pool de hilos del servidor (200 en Tomcat por defecto), locks con mucha contención, llamadas a servicios externos lentos sin timeout o el propio límite de conexiones de la base de datos.

Para confirmarlo: métricas de Actuator como hikaricp.connections.pending y hikaricp.connections.usage, el número de hilos ocupados de Tomcat y un volcado de hilos (jcmd <pid> Thread.print) durante la prueba para ver dónde esperan. Ampliar el pool sin más solo traslada el cuello de botella a la base de datos; a veces la solución es reducir el tiempo que cada petición retiene la conexión.

Qué está evaluando la pregunta

En recursos que se agotan sin gastar CPU: el pool de conexiones de la base de datos (HikariCP trae 10 por defecto), el pool de hilos del servidor, locks o llamadas externas lentas sin timeout. Las métricas de Actuator (hikaricp.connections.pending, hilos ocupados) y un volcado de hilos lo confirman.

Reto del tutor

Un equipo tiene 300 tests de API con REST Assured contra la aplicación completa y una base de datos real. La suite tarda 25 minutos y los desarrolladores se la saltan. Proponen borrar la mitad. Evalúa la situación.

Lo que una respuesta suele tener bien

Detectan que una suite lenta pierde su valor.

Lo que suele quedarse en la superficie

Proponer «paralelizar los tests» como única solución.

El concepto de fondo que suele faltar

No ver que la mayoría de esos tests comprueban validaciones y códigos de error que no necesitan la aplicación completa, y que borrar tests sin criterio deja sin cubrir flujos críticos.

Cómo lo respondería alguien senior en una entrevista

Reclasificar: la validación, los errores y el contrato HTTP pasan a @WebMvcTest con MockMvc (milisegundos); las reglas de negocio, a tests unitarios; en REST Assured se quedan los flujos críticos completos. Reutilizar un único contenedor de base de datos para toda la suite y separar unitarios (Surefire) de integración (Failsafe) en el pipeline.

Proyecto «Pedidos» · paso 21 de 30

Contrato de la API y prueba de carga

Asegurar el contrato HTTP y saber cuánta carga aguanta la API.

De qué va el proyecto

El backend de una tienda online: clientes, catálogo de productos y pedidos con sus líneas, pagos y envíos. Empieza como un proyecto Maven con records y termina como un sistema con API REST, seguridad, mensajería, contenedores y observabilidad. Cada paso construye sobre el anterior, así que trabaja siempre en el mismo repositorio.

Qué hacer

  1. Prueba con MockMvc la validación y los errores de POST /pedidos.
  2. Escribe con REST Assured el flujo crear → confirmar → pagar contra la aplicación arrancada.
  3. Crea un plan de JMeter con 50 usuarios que crean y consultan pedidos durante 2 minutos.

Está listo cuando

  • ./mvnw test ejecuta los unitarios y MockMvc; ./mvnw verify, además, los *IT.
  • Tienes anotados el p95, la tasa de errores y el primer recurso que se satura.

Katas: ejercicios cortos

Reglas de oro

  • Valida el contrato HTTP con MockMvc; reserva REST Assured para los flujos críticos.
  • En las pruebas de carga, mira percentiles y errores, no la media.
  • Los escenarios BDD describen negocio, no endpoints.
  • Unitarios con Surefire en cada push; integración con Failsafe en cada pull request.
Se guarda automáticamente

Fuentes oficiales

Revisado el . Este día en Markdown