Tutorial: Postman del CRUD y de las asociaciones
1. Objetivos de aprendizaje
Sección titulada «1. Objetivos de aprendizaje»Al terminar este tutorial vas a ser capaz de:
- Probar un CRUD completo con Postman verificando no solo el status code, sino también el cuerpo, los valores y los efectos de cada operación.
- Encadenar peticiones con variables: crear un recurso, usarlo y borrarlo.
- Probar escenarios negativos (
400y404) y comprobar que el cuerpo del error es unApiErrorcon un mensaje que nombra la causa. - Probar las asociaciones entre dos entidades: asociar, listar, repetir y desasociar.
- Correr toda la Collection con el Runner de Postman y con Newman, y comprobar que tus tests pueden fallar.
Prerrequisitos:
- El back del Release 7 corriendo (
./mvnw spring-boot:run), con el CRUD de tu entidad y el tutorial de endpoints de asociación. - La Collection y el Environment (
baseUrl) del tutorial de Postman del Release 2. - Al menos un registro en tus datos iniciales (
data.sql) que ya tenga un elemento asociado. En el ejemplo, un artista con un álbum. - Haber leído la sección Qué verificar más allá del status de la teoría.
2. El recorrido
Sección titulada «2. El recorrido»Vas a escribir 19 peticiones que se ejecutan en orden, de arriba hacia abajo.
Cada una depende de la anterior: la primera crea un artista y guarda su id en una
variable, las siguientes lo usan, y las últimas lo borran. Así la Collection crea
sus propios datos y los limpia al final, y se puede correr cuantas veces quieras sin
depender del estado en que haya quedado el back.
| Petición | Qué comprueba |
|---|---|
01 POST artistas |
Crear: 201, id asignado, valores iguales a lo enviado |
02 GET artistas/{id} |
El artista creado persistió |
03 GET artistas |
La lista incluye al artista creado |
04 PUT artistas/{id} |
Actualizar |
05 GET artistas/{id} |
El cambio persistió |
06 POST artistas |
Nombre repetido: 400 y mensaje de la regla |
07 POST artistas |
Sin nombre: 400 |
08 GET artistas/{id} |
Un id que no existe: 404 |
09 GET artistas/1 |
Toma un álbum que ya existe |
10 POST artistas/{id}/albumes/{albumId} |
Asociar |
11 GET artistas/{id}/albumes |
La asociación persistió |
12 POST |
Asociar dos veces: 400 |
13 POST |
Asociar un álbum que no existe: 404 |
14 PUT artistas/{id} |
Actualizar no borra las asociaciones |
15 DELETE .../albumes/{albumId} |
Desasociar: 204 |
16 GET artistas/{id}/albumes |
Ya no está |
17 DELETE |
Desasociar dos veces: 400 |
18 DELETE artistas/{id} |
Borrar: 204 |
19 GET artistas/{id} |
El artista borrado ya no existe: 404 |
3. Una prueba para todas las respuestas
Sección titulada «3. Una prueba para todas las respuestas»Casi todas las respuestas del back son JSON, así que verificar el Content-Type
en cada petición sería repetir el mismo test 19 veces. Postman permite escribirlo
una sola vez, en la Collection.
- Haz clic sobre el nombre de tu Collection para abrirla, abre la pestaña Scripts y elige Post-response: es el código que corre después de cada respuesta.
- Pega:
// Se ejecuta tras cada petición de la Collectionif (pm.response.code !== 204) { pm.test("Content-Type es JSON", () => { pm.expect(pm.response.headers.get("Content-Type")).to.include("application/json"); });}Un 204 No Content no trae cuerpo y por eso no tiene Content-Type: el if lo
excluye. Desde ahora, en Test Results de todas tus peticiones aparece también
este test.
4. El CRUD de un artista
Sección titulada «4. El CRUD de un artista»Petición 01: POST artistas
Sección titulada «Petición 01: POST artistas»Todo el recorrido empieza creando el artista con el que vas a trabajar. Así la Collection no depende de datos que otra persona pudo haber borrado.
- Haz clic derecho sobre la Collection → Add request y nómbrala
01 POST artistas. - Cambia el método a
POSTy escribe la URL{{baseUrl}}/artistas. - Abre la pestaña Scripts y elige Pre-request. Ese código corre antes de enviar la petición. Pega:
pm.environment.set("nombreArtista", "Artista de prueba " + Date.now());Date.now() devuelve un número distinto cada vez. Con él, el nombre es único y la
Collection se puede correr muchas veces sin chocar con la regla de nombre repetido.
- En Body, selecciona raw y formato
JSON, y pega:
{ "nombre": "{{nombreArtista}}", "paisOrigen": "Colombia", "biografia": "Creado por la Collection"}Postman reemplaza {{nombreArtista}} por el valor que guardó el script.
- En Scripts → Post-response, pega:
const artista = pm.response.json();
pm.test("Status 201 Created", () => pm.response.to.have.status(201));pm.test("El servidor asigna un id numérico positivo", () => { pm.expect(artista.id).to.be.a("number"); pm.expect(artista.id).to.be.above(0);});pm.test("El nombre y el país coinciden con lo enviado", () => { pm.expect(artista.nombre).to.equal(pm.environment.get("nombreArtista")); pm.expect(artista.paisOrigen).to.equal("Colombia");});
pm.environment.set("artistaId", artista.id);Los tests verifican tres capas distintas: el status, la forma del cuerpo (el
id es un número) y los valores (lo que volvió es lo que enviaste). La última
línea guarda el id que asignó el servidor: las peticiones siguientes lo usan como
{{artistaId}}.
- Haz clic en Send. En Test Results deben aparecer todos los tests con ✅.
Petición 02: GET artistas/{id}
Sección titulada «Petición 02: GET artistas/{id}»Ahora comprueba que el artista creado persistió: no basta con que el POST
haya respondido bien.
- Crea la petición
02 GET artistas/{id} (persistió), métodoGET, URL{{baseUrl}}/artistas/{{artistaId}}. - En Scripts → Post-response, pega:
const artista = pm.response.json();
pm.test("Status 200 OK", () => pm.response.to.have.status(200));pm.test("Es el artista creado, con el id de la URL", () => { pm.expect(artista.id).to.equal(Number(pm.environment.get("artistaId"))); pm.expect(artista.nombre).to.equal(pm.environment.get("nombreArtista"));});pm.test("DTOCompleto: albumes es un arreglo, vacío al inicio", () => { pm.expect(artista.albumes).to.be.an("array").that.is.empty;});- Haz clic en Send y confirma los ✅.
El GET por id responde un DTOCompleto: trae el campo albumes, aunque la
respuesta del POST no lo traía. Por eso este test verifica que sea un arreglo.
Petición 03: GET artistas
Sección titulada «Petición 03: GET artistas»- Crea la petición
03 GET artistas (lista), métodoGET, URL{{baseUrl}}/artistas. - En Scripts → Post-response, pega:
const lista = pm.response.json();
pm.test("Status 200 OK", () => pm.response.to.have.status(200));pm.test("Es un arreglo y cada elemento tiene id y nombre", () => { pm.expect(lista).to.be.an("array").that.is.not.empty; lista.forEach((a) => { pm.expect(a).to.have.property("id"); pm.expect(a.nombre).to.be.a("string"); });});pm.test("La lista incluye al artista recién creado", () => { const id = Number(pm.environment.get("artistaId")); pm.expect(lista.some((a) => a.id === id)).to.be.true;});- Haz clic en Send y confirma los ✅.
El último test no solo mira la forma de la lista: confirma que el artista que acabas de crear está en ella.
Petición 04: PUT artistas/{id}
Sección titulada «Petición 04: PUT artistas/{id}»- Crea la petición
04 PUT artistas/{id} (actualizar), métodoPUT, URL{{baseUrl}}/artistas/{{artistaId}}. - En Scripts → Pre-request, pega:
pm.environment.set("nombreEditado", "Editado " + Date.now());- En Body (raw,
JSON):
{ "nombre": "{{nombreEditado}}", "paisOrigen": "Perú"}- En Scripts → Post-response, pega:
const artista = pm.response.json();
pm.test("Status 200 OK", () => pm.response.to.have.status(200));pm.test("El id no cambia y el nombre es el nuevo", () => { pm.expect(artista.id).to.equal(Number(pm.environment.get("artistaId"))); pm.expect(artista.nombre).to.equal(pm.environment.get("nombreEditado"));});- Haz clic en Send y confirma los ✅.
Petición 05: GET artistas/{id}, el cambio persistió
Sección titulada «Petición 05: GET artistas/{id}, el cambio persistió»Un PUT que responde 200 con el cuerpo nuevo todavía no demuestra que el cambio
se guardó. Esta petición lo comprueba leyendo de nuevo.
- Crea la petición
05 GET artistas/{id} (el cambio persistió), métodoGET, URL{{baseUrl}}/artistas/{{artistaId}}. - En Scripts → Post-response, pega:
const artista = pm.response.json();
pm.test("Status 200 OK", () => pm.response.to.have.status(200));pm.test("El nombre y el país guardados son los nuevos", () => { pm.expect(artista.nombre).to.equal(pm.environment.get("nombreEditado")); pm.expect(artista.paisOrigen).to.equal("Perú");});- Haz clic en Send y confirma los ✅.
5. Escenarios negativos
Sección titulada «5. Escenarios negativos»Hasta aquí probaste el camino feliz. Ahora comprueba que el back rechaza lo que debe rechazar y que responde con un cuerpo útil.
Petición 06: POST artistas con nombre repetido
Sección titulada «Petición 06: POST artistas con nombre repetido»Tu Service tiene una regla: no puede haber dos artistas con el mismo nombre.
- Crea la petición
06 POST artistas con nombre repetido (400), métodoPOST, URL{{baseUrl}}/artistas. - En Body (raw,
JSON), usa el nombre que dejó elPUTde la petición 04, que ya existe:
{ "nombre": "{{nombreEditado}}"}- En Scripts → Post-response, pega:
const error = pm.response.json();
pm.test("Status 400 Bad Request", () => pm.response.to.have.status(400));pm.test("El cuerpo es un ApiError y el mensaje explica la regla", () => { pm.expect(error.status).to.equal(400); pm.expect(error.message).to.include("Ya existe"); pm.expect(error).to.have.property("timestamp");});- Haz clic en Send y confirma los ✅.
Fíjate en lo que se verifica además del 400: el cuerpo es un ApiError
(status, message, timestamp, el que escribiste en el
tutorial de DTOs y Controller) y el
message nombra la causa. Es el texto que tu front le muestra a la persona, así
que un mensaje que cambie por error es un defecto que el status no ve.
Petición 07: POST artistas sin nombre
Sección titulada «Petición 07: POST artistas sin nombre»- Crea la petición
07 POST artistas sin nombre (400), métodoPOST, URL{{baseUrl}}/artistas. - En Body (raw,
JSON):
{ "paisOrigen": "Chile"}- En Scripts → Post-response, pega:
const error = pm.response.json();
pm.test("Status 400 Bad Request", () => pm.response.to.have.status(400));pm.test("El mensaje dice que el nombre es obligatorio", () => { pm.expect(error.message).to.include("obligatorio");});- Haz clic en Send y confirma los ✅.
Petición 08: GET artistas/{id} que no existe
Sección titulada «Petición 08: GET artistas/{id} que no existe»- Crea la petición
08 GET artistas/{id} inexistente (404), métodoGET, URL{{baseUrl}}/artistas/999999. - En Scripts → Post-response, pega:
const error = pm.response.json();
pm.test("Status 404 Not Found", () => pm.response.to.have.status(404));pm.test("El cuerpo es un ApiError, no un artista", () => { pm.expect(error.status).to.equal(404); pm.expect(error.message).to.include("no existe"); pm.expect(error).to.not.have.property("nombre");});- Haz clic en Send y confirma los ✅.
El último test es una forma de decir “esto es un error y no un artista”: un cuerpo
de error no debe tener nombre.
6. La asociación con álbumes
Sección titulada «6. La asociación con álbumes»Estas peticiones prueban los endpoints del tutorial de asociaciones.
Petición 09: GET artistas/1, tomar un álbum que ya existe
Sección titulada «Petición 09: GET artistas/1, tomar un álbum que ya existe»La API no tiene un POST de álbumes, así que no puedes crear un álbum propio para la
prueba. En su lugar, esta petición lee un artista de tus datos iniciales y guarda el
id de uno de sus álbumes.
- Crea la petición
09 GET artistas/1 (tomar un álbum que ya existe), métodoGET, URL{{baseUrl}}/artistas/1. - En Scripts → Post-response, pega:
const artista = pm.response.json();
pm.test("Status 200 OK", () => pm.response.to.have.status(200));pm.test("El artista de los datos iniciales trae al menos un álbum", () => { pm.expect(artista.albumes).to.be.an("array").that.is.not.empty;});
pm.environment.set("albumId", artista.albumes[0].id);- Haz clic en Send y confirma los ✅.
Si el artista que trae álbumes en tus datos iniciales no es el 1, cambia el id de
la URL.
Petición 10: POST artistas/{id}/albumes/{albumId}, asociar
Sección titulada «Petición 10: POST artistas/{id}/albumes/{albumId}, asociar»- Crea la petición
10 POST artistas/{id}/albumes/{albumId} (asociar), métodoPOST, URL{{baseUrl}}/artistas/{{artistaId}}/albumes/{{albumId}}. No lleva cuerpo. - En Scripts → Post-response, pega:
const artista = pm.response.json();
pm.test("Status 200 OK (no se creó ningún recurso)", () => pm.response.to.have.status(200));pm.test("Devuelve el artista completo, ya con el álbum", () => { pm.expect(artista.id).to.equal(Number(pm.environment.get("artistaId"))); const ids = artista.albumes.map((a) => a.id); pm.expect(ids).to.include(Number(pm.environment.get("albumId")));});- Haz clic en Send y confirma los ✅.
Fíjate en el status: es 200 y no 201, porque no se creó ningún recurso nuevo, se
asociaron dos que ya existían. El cuerpo es el artista con su colección ya
actualizada.
Petición 11: GET artistas/{id}/albumes, la asociación persistió
Sección titulada «Petición 11: GET artistas/{id}/albumes, la asociación persistió»- Crea la petición
11 GET artistas/{id}/albumes (la asociación persistió), métodoGET, URL{{baseUrl}}/artistas/{{artistaId}}/albumes. - En Scripts → Post-response, pega:
const albumes = pm.response.json();
pm.test("Status 200 OK", () => pm.response.to.have.status(200));pm.test("La lista contiene el álbum asociado", () => { pm.expect(albumes).to.be.an("array"); pm.expect(albumes.map((a) => a.id)).to.include(Number(pm.environment.get("albumId")));});- Haz clic en Send y confirma los ✅.
Petición 12: asociar dos veces
Sección titulada «Petición 12: asociar dos veces»- Crea la petición
12 POST asociar de nuevo (400), métodoPOST, URL{{baseUrl}}/artistas/{{artistaId}}/albumes/{{albumId}}. - En Scripts → Post-response, pega:
const error = pm.response.json();
pm.test("Status 400 Bad Request", () => pm.response.to.have.status(400));pm.test("El mensaje dice que ya estaban asociados", () => { pm.expect(error.message).to.include("ya tiene");});- Haz clic en Send y confirma los ✅.
Petición 13: asociar un álbum que no existe
Sección titulada «Petición 13: asociar un álbum que no existe»- Crea la petición
13 POST asociar un álbum inexistente (404), métodoPOST, URL{{baseUrl}}/artistas/{{artistaId}}/albumes/999999. - En Scripts → Post-response, pega:
const error = pm.response.json();
pm.test("Status 404 Not Found", () => pm.response.to.have.status(404));pm.test("El mensaje habla del álbum", () => { pm.expect(error.message).to.include("álbum");});- Haz clic en Send y confirma los ✅.
Petición 14: el PUT no debe borrar las asociaciones
Sección titulada «Petición 14: el PUT no debe borrar las asociaciones»El PUT de un artista no menciona sus álbumes en el cuerpo. Un PUT mal escrito,
que guarda el objeto tal como llegó, dejaría al artista sin álbumes y respondería
200 igual. Para verlo, el test de esta petición envía una segunda petición.
- Crea la petición
14 PUT artistas/{id} conserva los álbumes, métodoPUT, URL{{baseUrl}}/artistas/{{artistaId}}. Ya asociaste un álbum a este artista en la petición 10. - En Body (raw,
JSON):
{ "nombre": "{{nombreEditado}}", "paisOrigen": "Ecuador"}- En Scripts → Post-response, pega:
pm.test("Status 200 OK", () => pm.response.to.have.status(200));
pm.sendRequest({ url: pm.environment.get("baseUrl") + "/artistas/" + pm.environment.get("artistaId"), method: "GET",}, (err, res) => { pm.test("Tras el PUT el artista sigue teniendo el álbum asociado", () => { pm.expect(err).to.be.null; const ids = res.json().albumes.map((a) => a.id); pm.expect(ids).to.include(Number(pm.environment.get("albumId"))); });});pm.sendRequest envía una petición desde el propio test. Cuando llega la respuesta,
ejecuta la función que le pasas. Aquí consulta el artista y comprueba que el álbum
sigue en su lista: es la capa de efectos.
- Haz clic en Send y confirma los ✅.
Petición 15: DELETE artistas/{id}/albumes/{albumId}, desasociar
Sección titulada «Petición 15: DELETE artistas/{id}/albumes/{albumId}, desasociar»- Crea la petición
15 DELETE artistas/{id}/albumes/{albumId} (desasociar), métodoDELETE, URL{{baseUrl}}/artistas/{{artistaId}}/albumes/{{albumId}}. - En Scripts → Post-response, pega:
pm.test("Status 204 No Content", () => pm.response.to.have.status(204));pm.test("No hay cuerpo", () => pm.expect(pm.response.text()).to.equal(""));- Haz clic en Send y confirma los ✅.
Petición 16: GET artistas/{id}/albumes, ya no está
Sección titulada «Petición 16: GET artistas/{id}/albumes, ya no está»- Crea la petición
16 GET artistas/{id}/albumes (ya no está), métodoGET, URL{{baseUrl}}/artistas/{{artistaId}}/albumes. - En Scripts → Post-response, pega:
pm.test("Status 200 OK", () => pm.response.to.have.status(200));pm.test("La lista quedó vacía", () => pm.expect(pm.response.json()).to.be.empty);- Haz clic en Send y confirma los ✅.
Petición 17: desasociar dos veces
Sección titulada «Petición 17: desasociar dos veces»- Crea la petición
17 DELETE desasociar de nuevo (400), métodoDELETE, URL{{baseUrl}}/artistas/{{artistaId}}/albumes/{{albumId}}. - En Scripts → Post-response, pega:
pm.test("Status 400 Bad Request", () => pm.response.to.have.status(400));pm.test("El mensaje dice que no estaban asociados", () => { pm.expect(pm.response.json().message).to.include("no tiene");});- Haz clic en Send y confirma los ✅.
7. Borrar y limpiar
Sección titulada «7. Borrar y limpiar»Petición 18: DELETE artistas/{id}
Sección titulada «Petición 18: DELETE artistas/{id}»- Crea la petición
18 DELETE artistas/{id}, métodoDELETE, URL{{baseUrl}}/artistas/{{artistaId}}. - En Scripts → Post-response, pega:
pm.test("Status 204 No Content", () => pm.response.to.have.status(204));pm.test("No hay cuerpo", () => pm.expect(pm.response.text()).to.equal(""));- Haz clic en Send y confirma los ✅.
Petición 19: GET artistas/{id} tras borrar
Sección titulada «Petición 19: GET artistas/{id} tras borrar»Un DELETE que responde 204 no demuestra que el recurso se borró. Esta petición lo
comprueba, y además deja el Environment limpio.
- Crea la petición
19 GET artistas/{id} tras borrar (404), métodoGET, URL{{baseUrl}}/artistas/{{artistaId}}. - En Scripts → Post-response, pega:
pm.test("Status 404: el artista ya no existe", () => pm.response.to.have.status(404));
pm.environment.unset("artistaId");pm.environment.unset("albumId");pm.environment.unset("nombreArtista");pm.environment.unset("nombreEditado");- Haz clic en Send y confirma el ✅.
Las cuatro líneas de unset borran las variables que usó el recorrido. La próxima
vez que corras la Collection, empieza desde cero.
8. Correr toda la Collection
Sección titulada «8. Correr toda la Collection»Hasta ahora enviaste las peticiones una por una. Ahora corre las 19 juntas, en orden.
Con el Runner de Postman
Sección titulada «Con el Runner de Postman»- Haz clic derecho sobre la Collection (o en sus tres puntos) → Run.
- Selecciona tu Environment y confirma que las 19 peticiones de este tutorial están
marcadas y en orden (más la
GET artistasdel Release 2, si la dejaste). - Haz clic en Run.
Todos los tests deben aparecer en verde. Córrela una segunda vez: también debe pasar. Si fallara la segunda vez, alguna petición está dejando datos sin limpiar.
Con Newman
Sección titulada «Con Newman»Newman corre la misma Collection desde la terminal, y es lo que usa el pipeline. Desde la raíz de tu repositorio (con tu back corriendo), tras exportar los archivos como se explica en el paso 10:
newman run collections/Artista.postman_collection.json -e collections/environment.postman_environment.jsonNewman imprime cada petición con sus tests y, al final, una tabla con el total de peticiones y de aserciones.
9. Comprobar que los tests pueden fallar
Sección titulada «9. Comprobar que los tests pueden fallar»Un test que nunca falla no sirve de nada. Para confiar en esta Collection, rompe el back a propósito y mira qué detecta. Después de cada prueba, deshaz el cambio.
Cambio 1: el mensaje de un error. En tu ArtistaService, cambia el texto de la
excepción "Ya existe un artista con ese nombre" por "Error". Reinicia el back y
corre la Collection. El status sigue siendo 400, pero falla el test de la
petición 06, “El cuerpo es un ApiError y el mensaje explica la regla”. Un test que
solo mirara el status no se habría enterado.
Cambio 2: asociar sin guardar. En tu ArtistaAlbumService, comenta la línea
artista.getAlbumes().add(album); de addAlbum. Reinicia el back y corre la
Collection. La petición 10 sigue respondiendo 200, pero fallan los tests de las
peticiones 10, 11, 12, 14 y 15: el álbum nunca quedó asociado.
10. Exportar la Collection y el Environment a tu repositorio
Sección titulada «10. Exportar la Collection y el Environment a tu repositorio»Exporta la Collection y el Environment como en el
tutorial del Release 2:
en la carpeta collections/, con los nombres Artista.postman_collection.json y
environment.postman_environment.json (sustituye Artista por el de tu entidad).
Sobrescribe los archivos que ya tenías y haz commit.
Si necesitas recuperarlos en otro computador, la subsección 10.1 del mismo tutorial del Release 2 explica cómo importarlos.
¿Dónde quedaste?
Sección titulada «¿Dónde quedaste?»Al final de este tutorial tienes:
- Una Collection de 19 peticiones que prueba el CRUD de una entidad y su asociación, verificando status, cabeceras, forma del cuerpo, valores y efectos.
- Escenarios negativos que comprueban el
ApiErrory el mensaje de cada regla. - Una Collection que crea sus propios datos y los borra, y que se puede correr varias veces seguidas.
- La Collection y el Environment exportados y versionados en
collections/, y la comprobación de que sus tests detectan fallos reales.
Lo que viene después: correr estas mismas Collections con Newman dentro del pipeline del grupo.