Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado revisar búsquedas y paginación. En Servidor continúas la implementación del mismo producto.
Se explica
25 minutos · explicación y demostración
Ya tienes consultas avanzadas comprobadas manualmente. MockMvc permite enviar peticiones al procesamiento web de Spring desde un test. OpenAPI describe el contrato de forma estructurada y Swagger UI ofrece una página para explorarlo. Hoy contrastarás pruebas y documentación.
La brecha entre el servicio y el protocolo HTTP
Hasta ahora has probado tus servicios con tests unitarios y tus repositorios con @DataJpaTest. Esas pruebas garantizan que la lógica de negocio y las consultas SQL funcionan.
Sin embargo, ninguna de esas pruebas valida la capa web:
- ¿Qué pasa si alguien cambia por error la ruta
@PostMapping("/proyectos")a@PostMapping("/proyecto")? - ¿Qué pasa si el controlador olvida la anotación
@Validy acepta cuerpos con campos en blanco? - ¿Qué pasa si el controlador devuelve un
200 OKplano en lugar de un201 Createdcon la cabeceraLocationobligatoria? - ¿Qué pasa si el serializador Jackson omite un campo o cambia el nombre de una propiedad en el JSON?
Para responder a estas preguntas sin tener que arrancar manualmente la aplicación y probar peticiones en Bruno una a una, utilizamos pruebas de slice web con MockMvc.
- Petición HTTP simulada (MockMvc)
- Filtros y Routing de Spring MVC
- Validación de DTOs (@Valid)
- Controlador (@RestController)
- Servicio simulado (@MockBean)
La ventaja de @WebMvcTest
@WebMvcTest no arranca un servidor HTTP real ni conecta con PostgreSQL.
Carga únicamente los componentes de la capa web (controladores, mappers de Jackson, validadores de Bean Validation y manejadores @RestControllerAdvice), ejecutando docenas de tests en menos de un segundo.
Sintaxis y aserciones fluidas con MockMvc y JSONPath
MockMvc utiliza un patrón fluido para construir la petición y comprobar las expectativas de la respuesta:
mockMvc.perform(post("/proyectos")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"nombre": "Portal Clientes", "descripcion": "Acceso web"}
"""))
.andExpect(status().isCreated())
.andExpect(header().exists("Location"))
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.nombre").value("Portal Clientes"));
Para evaluar el contenido del JSON utilizamos JSONPath:
$.id: el atributoidde la raíz del objeto.$.nombre: el atributonombre.$.content: el array de elementos en una respuesta paginada.$.content.length(): la cantidad de elementos devueltos en el array.$.content[0].titulo: el título del primer elemento de la lista.
La documentación como código vivo (Living Documentation)
En desarrollo de software profesional existe una regla demostrada por la experiencia: toda documentación que no se genere automáticamente a partir del código acaba mintiendo.
Un desarrollador añade un campo al DTO, renombra un query param o cambia un código de estado de 200 a 201. Si la documentación vive en un documento estático, nadie se acuerda de actualizarlo. Al cabo de dos meses, el equipo de frontend intenta consumir la API y nada encaja.
El principio de la documentación viva
El código fuente es la única fuente de verdad (Single Source of Truth).
Utilizamos el estándar OpenAPI 3.0 para que el propio framework inspeccione nuestros controladores, rutas y DTOs, generando una especificación técnica interactiva que se actualiza automáticamente con cada compilación.
OpenAPI frente a Swagger UI
Conviene distinguir con precisión ambos términos:
| Concepto | Qué es | Para qué sirve | Dónde se consulta |
|---|---|---|---|
| OpenAPI 3.0 | Estándar formal independiente de cualquier lenguaje que describe APIs REST en formato JSON o YAML. | Lo consumen las máquinas: generadores de código de clientes, pasarelas de API (API Gateways) y herramientas de pruebas automáticas. | http://localhost:8080/v3/api-docs |
| Swagger UI | Aplicación web interactiva que lee la especificación OpenAPI y la renderiza visualmente. | La consumen los humanos: permite a cualquier desarrollador explorar los endpoints, ver ejemplos y lanzar peticiones en vivo (Try it out). | http://localhost:8080/swagger-ui.html |
Se trabaja
140 minutos · implementación guiada sobre el proyecto propio
Paso 1 · Retomar el proyecto y preparar la comprobación
- Abre los controladores, la colección y los tests existentes. Elige un listado paginado y un caso de validación como primeras pruebas HTTP.
- Localiza
src/test/javaypom.xml; revisa qué dependencias y configuración requiere cada bloque antes de pegar sus ejemplos. - Anota método, ruta, entrada, estado y campos de salida del endpoint elegido. Ese mismo contrato debe aparecer en el test y en la documentación.
Paso 2 · Crear la suite de ProyectoControllerTest
Crea primero el archivo de test con imports, anotaciones y campos. @MockBean sustituye al servicio en ese contexto: prepara con when(...).thenReturn(...) la respuesta que esperas de él para cada caso. Si el controlador recibe más colaboradores, decláralos también. Añade los métodos de test siguientes dentro de la misma clase, antes de su última llave. Los JSON y los constructores de DTO deben coincidir con tu contrato actual, incluida la paginación; ejecuta un test antes de añadir el siguiente.
Crea src/test/java/com/ejemplo/gestor/controller/ProyectoControllerTest.java. Aislamos el controlador inyectando MockMvc y simulando el colaborador de negocio con @MockBean.
Los import estáticos del final son la parte que más se atasca, porque sin ellos post(...), status() o jsonPath(...) no compilan. Reprodúcelos literalmente:
package com.ejemplo.gestor.controller;
import com.ejemplo.gestor.dto.ProyectoRequest;
import com.ejemplo.gestor.dto.ProyectoResponse;
import com.ejemplo.gestor.error.RecursoNoEncontradoException;
import com.ejemplo.gestor.service.ProyectoService;
import com.ejemplo.gestor.service.TareaService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ProyectoController.class)
class ProyectoControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private ProyectoService proyectoService;
@MockBean
private TareaService tareaService;
- Qué carga
@WebMvcTest(ProyectoController.class)y qué no - Sí: ese controlador, Jackson, Bean Validation, los
@ControllerAdvicey los conversores HTTP. No: servicios, repositorios, la conexión a PostgreSQL ni ningún otro controlador. Por eso arranca en centésimas y por eso cada colaborador del controlador tiene que llegar como@MockBean. - Por qué hay que declarar
TareaServicesi el test no lo usa - Porque
ProyectoControllerlo recibe por constructor. El contexto no arranca si falta un bean que alguien necesita, aunque este test concreto no lo llame nunca. Si te olvidas, el fallo esNo qualifying bean of type ...TareaServicey ocurre antes de ejecutar ningún test. - Las tres partes de todo test
- Arrange: preparas los datos y programas qué debe devolver el
@MockBean(when(...).thenReturn(...)). Act: lanzas la petición conmockMvc.perform(...). Assert: encadenas los.andExpect(...). Verlas separadas te dice de un vistazo qué se estaba probando cuando un test falla dentro de seis meses.
Verificamos que un cuerpo válido responde con código 201, emite la cabecera Location correcta y devuelve el recurso creado:
@Test
void crearProyecto_conDatosValidos_devuelve201YLocation() throws Exception {
// 1. Arrange: preparamos el DTO de entrada y la respuesta simulada del servicio
ProyectoRequest request = new ProyectoRequest("Plataforma SaaS", "Gestión cloud");
ProyectoResponse response = new ProyectoResponse(
1L, "Plataforma SaaS", "Gestión cloud", true, LocalDateTime.now()
);
when(proyectoService.crearProyecto(any(ProyectoRequest.class))).thenReturn(response);
// 2. Act & Assert: ejecutamos la petición HTTP y evaluamos el contrato
mockMvc.perform(post("/proyectos")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(header().string("Location", "http://localhost/proyectos/1"))
.andExpect(jsonPath("$.id").value(1L))
.andExpect(jsonPath("$.nombre").value("Plataforma SaaS"))
.andExpect(jsonPath("$.activo").value(true));
}
Comprobamos que si el cliente envía un nombre en blanco, Bean Validation intercepta la petición antes de llegar al servicio y emite el formato estándar RFC 7807:
@Test
void crearProyecto_conNombreEnBlanco_devuelve400ProblemDetails() throws Exception {
// Nombre inválido (vacío)
ProyectoRequest requestInvalido = new ProyectoRequest("", "Descripción válida");
mockMvc.perform(post("/proyectos")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(requestInvalido)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.title").value("Error de validación"))
.andExpect(jsonPath("$.invalidParams.nombre").exists());
// Verificamos que el servicio jamás llegó a ejecutarse ante datos corruptos
verify(proyectoService, never()).crearProyecto(any());
}
Simulamos que el servicio lanza RecursoNoEncontradoException y comprobamos que el @RestControllerAdvice lo transforma limpiamente en un 404:
@Test
void obtenerPorId_cuandoNoExiste_devuelve404NotFound() throws Exception {
when(proyectoService.buscarPorId(999L))
.thenThrow(new RecursoNoEncontradoException("No existe proyecto con id 999"));
mockMvc.perform(get("/proyectos/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.status").value(404))
.andExpect(jsonPath("$.detail").value("No existe proyecto con id 999"));
}
}
Paso 3 · Ejecutar la batería en terminal
Ejecuta tu suite desde la terminal de tu IDE o consola:
./mvnw test -Dtest=ProyectoControllerTest
Observa la salida de Maven:
- La suite arranca en menos de 1.5 segundos.
- No se realizan conexiones TCP a PostgreSQL.
- Los 3 tests pasan en verde confirmando que rutas, DTOs, validaciones, códigos de estado y respuestas JSON están blindados.
Un test que solo has visto en verde no te ha demostrado que vigile algo. Provoca los tres fallos, uno a uno, y devuelve el código a su sitio después de cada uno:
| Rompe esto | El test que debe fallar | El mensaje que verás |
|---|---|---|
Cambia el @PostMapping para que devuelva 200 en vez de 201 |
crearProyecto_conDatosValidos… |
Status expected:<201> but was:<200> |
Quita la anotación @Valid del parámetro del controlador |
crearProyecto_conNombreEnBlanco… |
Status expected:<400> but was:<201> |
Comenta el @ExceptionHandler de RecursoNoEncontradoException |
obtenerPorId_cuandoNoExiste… |
Status expected:<404> but was:<500> |
Ese tercer caso es el más instructivo: sin el manejador, una excepción de negocio se convierte en un 500, que es el código que le dice a quien consume tu API «el fallo es mío», cuando en realidad había pedido algo que no existe.
Paso 4 · Si algo no sale como dice el guion
| Síntoma | Causa casi segura | Qué mirar |
|---|---|---|
No qualifying bean of type '…Service' al arrancar el test |
Falta un @MockBean |
Declara todos los colaboradores del constructor del controlador, los use el test o no |
cannot find symbol: method post/status/jsonPath |
Faltan los import estáticos |
Los tres import static …MockMvcRequestBuilders.*, …MockMvcResultMatchers.* y org.mockito.Mockito.* |
El Location esperado no coincide |
MockMvc no conoce tu dominio real | Dentro de MockMvc el host siempre es http://localhost, sin puerto |
Status expected:<201> but was:<415> |
Falta el tipo de contenido | Añade .contentType(MediaType.APPLICATION_JSON) a la petición |
El servicio simulado devuelve null |
El when(...) no encaja con la llamada real |
Usa any(ProyectoRequest.class): si programas when(servicio.crear(request)) con un objeto concreto y el controlador construye otro, Mockito no lo reconoce |
JSONPath "$.id" does not exist |
La respuesta no es la que crees | Encadena .andDo(print()) antes de los andExpect para volcar en consola la respuesta entera |
Paso 5 · Batería de pruebas para TareaController
Aplica el mismo patrón para blindar el contrato de TareaController:
- Crea
TareaControllerTestanotada con@WebMvcTest(TareaController.class)y declara como@MockBeantodos los colaboradores que reciba el controlador. - Escribe
crearTarea_conDatosValidos_devuelve201YLocation, calcado del caso feliz del paso 2. - Escribe
crearTarea_conPrioridadInvalida_devuelve400, comprobando además converify(..., never())que el servicio no llegó a ejecutarse. - Escribe
listarTareas_conFiltros_devuelveListaPaginada200verificando que devuelve el array en$.contenty los metadatos$.page.totalElements. - Escribe
obtenerTarea_cuandoNoExiste_devuelve404, programando el simulacro para que lanceRecursoNoEncontradoException. - Escribe el subrecurso:
listarTareasDeProyecto_cuandoElProyectoNoExiste_devuelve404. Esta es la regla que implementaste en la sesión 29 y hasta ahora solo habías comprobado a mano. - Ejecuta
./mvnw test(la suite completa, no solo esta clase) y confirma que siguen en verde los tests de service de la UD4 y los de repositorio de la UD5. Tienes ya los tres niveles de la pirámide.
- Cómo saber que lo has terminado
- Seis tests nuevos en verde; cada test de rechazo comprueba el código de estado y que el servicio no se invocó; y has visto fallar en rojo, al menos una vez, cada uno de los tres del guion.
OpenAPI y Swagger
Paso 6 · Integrar y documentar con springdoc-openapi
Añade la dependencia dentro del bloque dependencies existente de pom.xml y sincroniza Maven. Crea config/OpenApiConfig.java e importa los tipos de io.swagger.v3.oas.models que utiliza; después añade las anotaciones de documentación a los métodos existentes. No reemplaces un controlador completo por el fragmento que solo muestra el POST. Arranca, abre /v3/api-docs y después Swagger UI: la primera URL comprueba el documento y la segunda su interfaz.
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.6</version>
</dependency>
La versión importa, y aquí no la gestiona Spring Boot
Springdoc no forma parte de Spring Boot, así que su versión no la fija el spring-boot-starter-parent: la escribes tú, y tiene que corresponder con la versión de Boot que usas. La rama 2.8.x es la que acompaña a Spring Boot 3.5; una anterior arranca pero deja Swagger vacío o falla al generar el esquema, y el error no dice en ningún momento que el problema sea de versiones.
Si actualizas Spring Boot en el futuro, esta es una de las dependencias que hay que revisar a mano.
Al compilar y arrancar la aplicación, Spring Boot habilitará automáticamente los endpoints de documentación sin necesidad de escribir una sola línea de configuración inicial.
Antes de anotar una sola clase, arranca y abre http://localhost:8080/swagger-ui.html. Ya tienes una documentación completa que no has escrito.
Léela con ojo crítico y anota en tu cuaderno tres cosas que un desarrollador externo no podría deducir de ahí. La lista suele salir así:
- Los endpoints aparecen agrupados bajo un nombre horrible del tipo
proyecto-controller. - Ningún método explica qué hace: solo se ve la firma.
- Solo aparece el código de respuesta feliz. Nada dice que un nombre duplicado devuelve
409, ni que un nombre vacío devuelve400con formato RFC 7807. - Los campos de los DTO no traen ejemplo, así que quien pruebe Try it out tiene que inventarse los valores.
Esas cuatro carencias son exactamente lo que arreglan los pasos 3 a 5. Documentar no es activar Swagger: es rellenar lo que Swagger no puede adivinar.
Creamos una clase de configuración para definir el título, descripción y versión de nuestra API:
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("API de Gestión de Proyectos e Incidencias")
.version("1.0.0")
.description("Servicio RESTful con persistencia relacional en PostgreSQL, filtros dinámicos y paginación.")
.contact(new Contact()
.name("Equipo de Ingeniería Backend")
.email("backend@empresa.com")));
}
}
Decoramos ProyectoController para estructurar la interfaz en bloques lógicos y documentar el propósito de cada método:
@Tag(name = "Proyectos", description = "Endpoints para la gestión del ciclo de vida de proyectos de desarrollo")
@RestController
@RequestMapping("/proyectos")
public class ProyectoController {
private final ProyectoService proyectoService;
public ProyectoController(ProyectoService proyectoService) {
this.proyectoService = proyectoService;
}
@Operation(
summary = "Crear un nuevo proyecto",
description = "Registra un proyecto con nombre único y estado activo por defecto. Emite cabecera Location con la URI del nuevo recurso."
)
@ApiResponses({
@ApiResponse(responseCode = "201", description = "Proyecto creado exitosamente"),
@ApiResponse(responseCode = "400", description = "Datos de entrada inválidos (Bean Validation)"),
@ApiResponse(responseCode = "409", description = "Conflicto: ya existe un proyecto con ese nombre")
})
@PostMapping
public ResponseEntity<ProyectoResponse> crear(@Valid @RequestBody ProyectoRequest request) {
ProyectoResponse nuevo = proyectoService.crearProyecto(request);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(nuevo.id())
.toUri();
return ResponseEntity.created(location).body(nuevo);
}
Anotamos los atributos de nuestros records para mostrar descripciones claras y ejemplos reales en Swagger:
@Schema(description = "Datos para el registro o actualización de un proyecto")
public record ProyectoRequest(
@Schema(description = "Nombre único del proyecto en la organización", example = "Portal de Clientes B2B")
@NotBlank(message = "El nombre no puede estar en blanco")
@Size(min = 3, max = 80, message = "El nombre debe tener entre 3 y 80 caracteres")
String nombre,
@Schema(description = "Descripción detallada de los objetivos del proyecto", example = "Migración de la interfaz corporativa a arquitectura desacoplada")
@Size(max = 500, message = "La descripción no puede superar 500 caracteres")
String descripcion
) {}
- Los imports de las anotaciones
- Todas viven bajo
io.swagger.v3.oas.annotations:…annotations.tags.Tag,…annotations.Operation,…annotations.Parameter,…annotations.media.Schemay…annotations.responses.ApiResponse/ApiResponses. Cuidado conTag: tu IDE te ofrecerá primero el de JUnit, que no es este. @Schemasobre unrecord- Se coloca delante de cada componente, en la misma línea o encima, tal y como se hace con
@NotBlank. Las anotaciones de validación que ya tenías desde la UD3 también se documentan solas:@Size(min=3, max=80)aparece en Swagger comominLength: 3, maxLength: 80sin que hagas nada. Es la recompensa de haber validado con anotaciones en vez de conif. - Por qué el
@ApiResponsede error hay que escribirlo a mano - Springdoc lee los tipos, no la lógica. Puede deducir qué devuelve tu método cuando todo va bien, pero no puede saber que tu servicio lanza
NombreDuplicadoExceptiony que tu@RestControllerAdvicela convierte en un409. Ese conocimiento solo está en tu cabeza, y por eso se declara.
Paso 7 · Explorar Swagger UI en vivo
Arranca tu aplicación Spring Boot y realiza estas comprobaciones:
-
Abrir Swagger UI: Navega en tu navegador a
http://localhost:8080/swagger-ui.html.- Comprueba que aparece el título «API de Gestión de Proyectos e Incidencias» y el bloque agrupado «Proyectos».
-
Examinar esquemas de datos: Baja a la sección inferior de Schemas.
- Comprueba que
ProyectoRequestmuestra las descripciones de los campos, los ejemplos y qué atributos son obligatorios.
- Comprueba que
-
Lanzar una petición interactiva (Try it out):
- Despliega
POST /proyectos, pulsa en Try it out, edita el JSON de ejemplo y pulsa Execute. - Comprueba que la consola responde con código
201 Createdy muestra las cabeceras de respuesta reales.
- Despliega
-
Inspeccionar la especificación pura:
- Abre
http://localhost:8080/v3/api-docsen una pestaña nueva para ver el documento JSON completo que consumirán los clientes automatizados.
- Abre
-
Comparar antes y después: vuelve a leer las tres carencias que anotaste en el paso 2 y comprueba, una por una, que ya no están.
Paso 8 · Si algo no sale como dice el guion
| Síntoma | Causa casi segura | Qué mirar |
|---|---|---|
/swagger-ui.html devuelve 404 |
La ruta redirige y el navegador no la sigue | Prueba http://localhost:8080/swagger-ui/index.html; si esa funciona, es solo la redirección |
| Swagger carga pero está vacío | Springdoc no encuentra tus controladores | Deben estar en un subpaquete de donde vive GestorApplication |
Los @Schema de los DTO no se ven |
Estás anotando la clase pero no los componentes | En un record, cada @Schema va delante de su componente |
cannot find symbol: class Tag |
Import equivocado | Debe ser io.swagger.v3.oas.annotations.tags.Tag, no el de JUnit |
Try it out devuelve 403 en las escrituras |
Es el CSRF de Spring Security | Todavía no aplica: llegará en la UD9, y allí se resuelve |
Paso 9 · Documentar los endpoints de Tareas y Filtros
Documenta el controlador de tareas aplicando las anotaciones correspondientes:
- Añade
@Tag(name = "Tareas", description = "Gestión de tareas, filtros multicriterio y paginación")enTareaController. - Documenta el endpoint de búsqueda
GET /tareasdecorando cada parámetro@RequestParamcon@Parameter:@Parameter(description = "Filtro por identificador del proyecto asociado", example = "1") @RequestParam(required = false) Long proyectoId - Añade ejemplos descriptivos a
TareaRequestyTareaDetalleResponsecon@Schema. - Recarga Swagger UI y verifica que la documentación de tareas permite filtrar interactivamente desde la propia página web.
- Documenta los errores de todos los endpoints de tareas, que es la parte que Swagger no puede deducir:
400de validación,404cuando el proyecto padre no existe (la regla de la sesión 29) y409si tu dominio tiene alguna restricción de unicidad. Usa@ApiResponsepara cada uno. - Documenta la paginación de la sesión 30: cada
@RequestParamdepage,sizeysortdebe llevar su@Parametercondescriptionyexample, porque son justo los que un consumidor externo no puede adivinar. - La prueba del consumidor: dale la URL de tu Swagger a un compañero, sin explicarle nada de palabra, y pídele que cree un proyecto con una tarea dentro usando solo Try it out. Cada vez que tenga que preguntarte algo, apunta la pregunta: cada una es un
@Schemao un@Operationque te falta. Corrígelos y repite.
- Cómo saber que lo has terminado
- Los dos bloques se llaman «Proyectos» y «Tareas», no
proyecto-controller; cada endpoint declara sus códigos de error además del feliz; todos los campos de los DTO traen ejemplo; y un compañero ha conseguido usar tu API entera desde Swagger sin preguntarte nada.
Paso 10 · Comprobar y registrar el resultado del proyecto
- Ejecuta los tests HTTP con un caso válido, uno rechazado y uno ausente. Comprueba estado y contenido significativo, no solo que la petición termine.
- Abre Swagger UI y compara sus parámetros, ejemplos y estados con las respuestas reales de la colección.
Ampliación si has completado el trabajo
Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.
Reto · Validación de cabeceras de caché HTTP (ETag y Cache-Control)
En APIs REST de alto rendimiento, los endpoints de consulta devuelven cabeceras de control de caché para que los clientes no descarguen datos repetidos si no han cambiado.
Diseña un test con MockMvc que verifique el soporte de cabeceras condicionales:
- Simula una petición
GET /proyectos/1que incluya la cabeceraIf-None-Match: "v1-abc". - Si el recurso no ha cambiado, comprueba que el endpoint devuelve código
304 Not Modifiedcon el cuerpo completamente vacío. - Analiza qué ahorro de ancho de banda y procesamiento representa este mecanismo para una API consumida por miles de clientes simultáneos.
ProyectoControllerTest cubriendo casos 201 y 400 con MockMvc y JSONPath.TareaControllerTest completa incluyendo validaciones, 404 y respuestas paginadas.Ver respuestas
1 · Porque no levanta el contexto completo de Spring: ignora repositorios, conexiones JDBC a base de datos y servicios, cargando únicamente los componentes del dispatcher web.
2 · Reemplaza el servicio real en el contexto de Spring por un doble de prueba de Mockito, permitiendo definir respuestas simuladas (when/then) e inspeccionar llamadas sin ejecutar lógica de negocio real.
3 · Mediante andExpect(header().exists("Location")) o andExpect(header().string("Location", valorEsperado)).
4 · jsonPath("$.page.totalElements").value(numeroEsperado) o jsonPath("$.totalElements").value(...).
Reto · Generación de clientes TypeScript con openapi-generator
El mayor superpoder de OpenAPI no es que los humanos lean Swagger UI: es que las máquinas generen código sin fallos humanos.
Investiga cómo funciona la herramienta de código abierto openapi-generator-cli:
- ¿Cómo permite el comando:
npx @openapitools/openapi-generator-cli generate -i http://localhost:8080/v3/api-docs -g typescript-axios -o ./frontend/apigenerar automáticamente todas las interfaces TypeScript y llamadas Axios para un frontend en React o Vue? - Si cambias el tipo de un campo en Java de
LongaStringy vuelves a ejecutar el generador, ¿cómo detecta el compilador de TypeScript el error en el frontend antes de que la aplicación llegue a producción?
Formato de entrega
Incluye esta explicación en el registro de la sesión dentro del repositorio de GitHub, junto al código y las comprobaciones. La entrega es el enlace al repositorio y al commit de la sesión.
springdoc-openapi integrada y Swagger UI accesible en /swagger-ui.html.@Tag, @Operation, @ApiResponses y @Schema con ejemplos.Ver respuestas
1 · Porque inspecciona directamente las anotaciones y clases compiladas de Java en cada ejecución; si el código cambia, la documentación cambia de forma simultánea e inmediata.
2 · /v3/api-docs devuelve el documento JSON estandarizado OpenAPI para ser procesado por herramientas y librerías; /swagger-ui.html es la interfaz gráfica web interactiva para usuarios humanos.
3 · Para proporcionar valores de ejemplo representativos que aparecen precargados en la documentación interactiva, facilitando las pruebas de consumo.
4 · Mediante la anotación @Tag(name = "NombreGrupo", description = "...") a nivel de clase controladora.
Cierre
15 minutos · resultado comprobable y explicación individual
Al terminar la sesión:
Una modificación incompatible hace fallar el test y la documentación describe el contrato ejecutado.
Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.