← Peticiones, respuestas y CRUD en memoria

Sesión 7 · Semana 4

Colección ejecutable y entornos

Punto de partida. Continúas con el contrato HTTP de la sesión 6: 201 y Location al crear, 404 para un detalle ausente y 204 al borrar. Hoy convertirás las peticiones en una colección que prepare sus datos, compruebe resultados y pueda repetirse. No necesitas crear otra API.

Se explica

25 minutos · explicación y demostración

De una petición manual a una prueba repetible

Una colección organiza peticiones guardadas. Una aserción compara un resultado observado con uno esperado y falla si no coincide. Un conjunto de peticiones solo constituye una prueba útil cuando sus aserciones comprueban lo que promete el contrato: estado, datos y efectos posteriores.

Una regresión es un comportamiento que funcionaba y deja de hacerlo después de un cambio. Por ejemplo, un DELETE podría seguir respondiendo 204 aunque ya no borrase nada. Un GET posterior al borrado permite detectar ese fallo.

Variables, entorno y datos de prueba

Una variable guarda un valor con nombre. {{baseUrl}} sustituye la dirección del backend y {{tareaId}} el identificador que acaba de devolver un POST. Así la prueba utiliza sus propios datos en lugar de depender del registro 1.

Un entorno agrupa valores de configuración: hoy baseUrl será http://localhost:8080. Cambiar de puerto solo requerirá modificar ese valor. La colección seguirá teniendo las mismas rutas y comprobaciones.

Escenario que prepara y comprueba sus propios datos
  1. Crear registros y guardar sus ids
  2. Consultar y comprobar los datos
  3. Modificar y volver a consultar
  4. Borrar y comprobar la ausencia
  5. Repetir sin depender de la ejecución anterior

Se trabaja

140 minutos · colección, aserciones y diagnóstico

Dedica aproximadamente 15 minutos a preparar la herramienta, 15 al entorno, 25 al encadenamiento, 30 a las aserciones, 20 a ejecutar y depurar, 20 a probar una regresión y 15 a comprobar la reproducción desde otra copia. Continúa usando una rama y una PR para los cambios del proyecto.

Paso 1 · Preparar la colección · 15 min

Si utilizas Postman: el cliente ligero sin cuenta de la sesión 3 permite enviar peticiones, pero para organizar colecciones y entornos necesitas iniciar sesión y entrar en un workspace, o espacio de trabajo. Sigue la guía de cuenta de Postman. Crea un espacio personal y utiliza New → Collection para crear una colección con el nombre de tu proyecto. Guarda allí las peticiones anteriores mediante Save. Si empezaste sin cuenta, puedes trasladar el historial al espacio de trabajo al iniciar sesión.

Si utilizas Bruno: selecciona Create Collection, escribe el nombre del proyecto y elige una carpeta dentro de su repositorio. Crea las peticiones con New Request, elige método y URL y guárdalas. Los archivos de la colección permanecen en esa carpeta. Sigue los bloques de Bruno de esta sesión: sus scripts no utilizan la API pm de Postman.

Elige una herramienta y completa todo el escenario con ella. Crea una carpeta por entidad y utiliza nombres como «Crear tarea de prueba» o «Consultar tarea borrada». Arranca el backend y comprueba manualmente una creación y su consulta antes de automatizarlas.

Paso 2 · Definir y seleccionar el entorno · 15 min

Un entorno guarda valores de configuración asociados al despliegue (como la dirección baseUrl), mientras que las variables de colección que utilizarás en el siguiente paso guardan datos dinámicos generados durante la prueba (como tareaId) para comunicarlos entre peticiones consecutivas.

  1. En Postman, abre Environments, crea uno llamado Local y añade baseUrl con valor http://localhost:8080. Guarda y selecciónalo en el selector de entorno.
  2. En Bruno, utiliza el selector Environment → Configure, crea Local, añade la misma variable, guarda y selecciona ese entorno.
  3. Sustituye el inicio de cada URL por {{baseUrl}}. El listado queda como {{baseUrl}}/tareas, adaptado al nombre de tu recurso.
  4. Envía el listado. Si la variable no se resuelve, comprueba su nombre, el entorno seleccionado y que has guardado el valor.
  5. Para comprobar que se aplica, añade temporalmente server.port=8081 en application.properties, reinicia y cambia solo baseUrl a http://localhost:8081. Repite el GET y restaura después el puerto y el entorno a 8080.

Paso 3 · Guardar el identificador devuelto · 25 min

Crea una petición POST llamada «Crear tarea A», con cuerpo JSON de prueba:

{
  "titulo": "Prueba A de la colección",
  "prioridad": "alta",
  "completada": false
}

Adapta campos y valores a tu dominio. Guarda antes de enviar. El cuerpo de la petición contiene datos; el script que lee la respuesta se coloca en otra sección.

En Postman → Scripts → Post-response, escribe:

pm.collectionVariables.unset("tareaId");
if (pm.response.code === 201) {
    pm.collectionVariables.set("tareaId", pm.response.json().id);
}

En Bruno → Script → Post Response, utiliza en su lugar:

bru.setVar("tareaId", "");
if (res.getStatus() === 201) {
    bru.setVar("tareaId", res.getBody().id);
}

Se borra el valor anterior para no reutilizar un id antiguo si el alta falla. Envía el POST y localiza tareaId en las variables de colección de Postman o en las variables de ejecución de Bruno. Crea una petición GET con {{baseUrl}}/tareas/{{tareaId}}: debe devolver el registro A.

Duplica el alta para crear «Tarea B», con un título distinto. Cambia su script para guardar tareaBId, sin sobrescribir tareaId. Tener dos registros permite detectar un controlador que devuelve siempre el primero en lugar del solicitado.

Paso 4 · Añadir comprobaciones automáticas · 30 min

En el POST de A, añade estas pruebas debajo del script de captura en Postman → Post-response:

pm.test("Creación con 201", function () {
    pm.response.to.have.status(201);
});
pm.test("Id asignado y contenido esperado", function () {
    const cuerpo = pm.response.json();
    pm.expect(cuerpo.id).to.be.above(0);
    pm.expect(cuerpo.titulo).to.equal("Prueba A de la colección");
});
pm.test("Incluye Location", function () {
    pm.expect(pm.response.headers.get("Location")).to.be.a("string").and.not.empty;
});

En Bruno → Tests, las pruebas equivalentes son:

test("Creación con 201", function () {
    expect(res.getStatus()).to.equal(201);
});
test("Id asignado y contenido esperado", function () {
    expect(res.getBody().id).to.be.above(0);
    expect(res.getBody().titulo).to.equal("Prueba A de la colección");
});
test("Incluye Location", function () {
    expect(res.getHeaders().location).to.be.a("string").and.not.empty;
});

Una prueba tiene un nombre y una condición. .equal(...) compara valores; .above(0) exige un número positivo. Un fallo en una condición aparece en los resultados de pruebas de la respuesta. Para B, cambia el título esperado en su prueba y conserva su variable independiente.

Comprueba también la identidad al consultar B. En su GET, además de exigir 200, compara id y título. En Postman:

pm.test("Devuelve exactamente B", function () {
    const cuerpo = pm.response.json();
    pm.expect(cuerpo.id).to.equal(Number(pm.collectionVariables.get("tareaBId")));
    pm.expect(cuerpo.titulo).to.equal("Prueba B de la colección");
});

En Bruno:

test("Devuelve exactamente B", function () {
    expect(res.getBody().id).to.equal(Number(bru.getVar("tareaBId")));
    expect(res.getBody().titulo).to.equal("Prueba B de la colección");
});

Construye las demás aserciones adaptando estos ejemplos. Ten en cuenta que las variables guardadas en la herramienta se recuperan siempre como cadenas de texto (string). Usamos Number(...) para convertir la variable a número antes de compararla; si se omite, la prueba fallará por discrepancia de tipos (expected 2 to equal '2').

Un listado debe ser un array; en Postman se comprueba con pm.expect(pm.response.json()).to.be.an("array") y en Bruno con expect(res.getBody()).to.be.an("array"), dentro de una prueba con nombre.

En DELETE exige 204 y cuerpo vacío. No intentes llamar a pm.response.json(), ya que al no haber cuerpo provocará un fallo de lectura (Unexpected end of JSON input). Utiliza en su lugar:

En Postman:

pm.test("Borrado con 204 y sin cuerpo", function () {
    pm.response.to.have.status(204);
    pm.expect(pm.response.text()).to.equal("");
});

En Bruno:

test("Borrado con 204 y sin cuerpo", function () {
    expect(res.getStatus()).to.equal(204);
    expect(res.getBody()).to.equal("");
});

Para el GET posterior al borrado, exige 404.

Paso 5 · Ejecutar el escenario completo · 20 min

Ordena estas peticiones en la carpeta de la entidad. Cada una debe contener sus propias aserciones; adapta los nombres de variables si tu recurso tiene otro nombre.

Orden Petición Comprobación
1 Listado 200 y array; puede contener datos anteriores.
2 Crear A 201, datos y Location; guardar tareaId.
3 Crear B 201 y título distinto; guardar tareaBId.
4 Consultar B 200, id y título de B, no de A.
5 PATCH de A con {"prioridad":"baja"} 200 y prioridad modificada.
6 Consultar A 200, prioridad baja y título original conservado.
7 POST con JSON mal formado, como {"titulo":} 400; todavía no confundas JSON mal formado con {}, que se acepta hasta incorporar validación.
8 POST con texto y Content-Type: text/plain 415. Elige raw y cambia el desplegable a Text (asigna la cabecera automáticamente).
9 Borrar A 204 y cuerpo vacío.
10 Consultar A borrada 404 usando el id capturado.
11 Borrar B 204 y cuerpo vacío.
12 Consultar B borrada 404.

Para la petición 8 (415), configura el cuerpo como raw y selecciona Text en el selector de formato de Postman o Bruno; esto ajusta la cabecera Content-Type: text/plain automáticamente sin necesidad de añadir cabeceras duplicadas a mano.

En Postman utiliza Run collection y selecciona la carpeta y el orden; en Bruno, Run sobre la colección o carpeta y comprueba el orden antes de ejecutar. Guarda los scripts y las peticiones antes de iniciar el recorrido.

Ejecuta dos veces sin reiniciar el servidor. Los ids cambiarán, pero las comprobaciones deben seguir pasando. Reinicia y repite: la colección debe preparar sus datos desde la memoria vacía. Si un POST falla, localiza primero esa causa; no interpretes los errores posteriores de variables como averías independientes.

Alcance de un resultado correcto

Un resultado verde solo cubre las aserciones ejecutadas. Si no comparas el título o no consultas después del borrado, esas propiedades quedan sin comprobar aunque todo aparezca en verde.

Paso 6 · Demostrar que detecta regresiones · 20 min

En una rama local de diagnóstico, introduce una avería cada vez. No modifiques las aserciones para adaptarlas al fallo:

  1. Cambia la creación para responder 200 en lugar de 201. Reinicia y ejecuta: debe fallar la aserción del alta.
  2. Restituye el POST y comprueba la recuperación. Después quita temporalmente el borrado de la lista, conservando el 204. El GET posterior debe detectar que el recurso sigue existiendo.
  3. Restituye DELETE y prueba que el detalle devuelva siempre la primera tarea. La consulta de B debe fallar al comparar identidad y contenido.
  4. Restaura el código y ejecuta de nuevo. No integres las averías. Si una ejecución fallida dejó registros, bórralos mediante sus ids o reinicia este servidor en memoria antes de repetir la prueba.

Anota para cada avería qué aserción la detectó. Si alguna pasó inadvertida, añade una comprobación que observe el comportamiento que faltaba.

Paso 7 · Conservar y reproducir el trabajo · 15 min

En Postman, exporta la colección y el entorno local mediante sus opciones Export y conserva ambos en el repositorio. Incluye solo datos de prueba y la dirección local; los ids se capturan durante cada ejecución. En Bruno, guarda la colección y el entorno en su carpeta versionada. No basta con que existan únicamente en la herramienta de tu ordenador.

Comprueba desde otra copia del repositorio que se puede importar o abrir la colección, seleccionar Local, arrancar el backend y ejecutar el escenario. Explica esos pasos brevemente en el README. Publica la rama y solicita la revisión de las pruebas antes de integrar.

Ampliación si has completado el trabajo

Aplica el escenario a la segunda entidad utilizando variables distintas. Añade un PUT completo y comprueba que el GET posterior refleja todos sus campos editables. Diseña además un caso propio que la colección actual no detecte: escribe primero la aserción, provoca el fallo y verifica su corrección. Mantén todo en el mismo proyecto.

Cierre

15 minutos · resultado comprobable y explicación individual

Al terminar, la colección prepara dos registros, usa sus ids reales, comprueba contenido y estados y elimina sus datos de prueba. Funciona dos veces seguidas y después de reiniciar. Puedes señalar una ejecución fallida por una regresión y su recuperación, y otra persona dispone de la colección y del entorno necesarios para reproducirla.

¿Por qué un GET de B detecta un error que consultar únicamente A podría ocultar?
¿Por qué no basta con comprobar el 204 de un DELETE?
¿Qué diferencia hay entre baseUrl y el identificador capturado después del POST?