Ir al contenido

Tutorial: Postman del CRUD y de las asociaciones

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 (400 y 404) y comprobar que el cuerpo del error es un ApiError con 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:


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

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.

  1. 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.
  2. Pega:
// Se ejecuta tras cada petición de la Collection
if (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.


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.

  1. Haz clic derecho sobre la Collection → Add request y nómbrala 01 POST artistas.
  2. Cambia el método a POST y escribe la URL {{baseUrl}}/artistas.
  3. 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.

  1. 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.

  1. 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}}.

  1. Haz clic en Send. En Test Results deben aparecer todos los tests con ✅.

Ahora comprueba que el artista creado persistió: no basta con que el POST haya respondido bien.

  1. Crea la petición 02 GET artistas/{id} (persistió), método GET, URL {{baseUrl}}/artistas/{{artistaId}}.
  2. 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;
});
  1. 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.

  1. Crea la petición 03 GET artistas (lista), método GET, URL {{baseUrl}}/artistas.
  2. 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;
});
  1. 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.

  1. Crea la petición 04 PUT artistas/{id} (actualizar), método PUT, URL {{baseUrl}}/artistas/{{artistaId}}.
  2. En Scripts → Pre-request, pega:
pm.environment.set("nombreEditado", "Editado " + Date.now());
  1. En Body (raw, JSON):
{
"nombre": "{{nombreEditado}}",
"paisOrigen": "Perú"
}
  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 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"));
});
  1. 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.

  1. Crea la petición 05 GET artistas/{id} (el cambio persistió), método GET, URL {{baseUrl}}/artistas/{{artistaId}}.
  2. 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ú");
});
  1. Haz clic en Send y confirma los ✅.

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.

  1. Crea la petición 06 POST artistas con nombre repetido (400), método POST, URL {{baseUrl}}/artistas.
  2. En Body (raw, JSON), usa el nombre que dejó el PUT de la petición 04, que ya existe:
{
"nombre": "{{nombreEditado}}"
}
  1. 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");
});
  1. 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.

  1. Crea la petición 07 POST artistas sin nombre (400), método POST, URL {{baseUrl}}/artistas.
  2. En Body (raw, JSON):
{
"paisOrigen": "Chile"
}
  1. 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");
});
  1. 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»
  1. Crea la petición 08 GET artistas/{id} inexistente (404), método GET, URL {{baseUrl}}/artistas/999999.
  2. 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");
});
  1. 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.


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.

  1. Crea la petición 09 GET artistas/1 (tomar un álbum que ya existe), método GET, URL {{baseUrl}}/artistas/1.
  2. 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);
  1. 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»
  1. Crea la petición 10 POST artistas/{id}/albumes/{albumId} (asociar), método POST, URL {{baseUrl}}/artistas/{{artistaId}}/albumes/{{albumId}}. No lleva cuerpo.
  2. 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")));
});
  1. 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ó»
  1. Crea la petición 11 GET artistas/{id}/albumes (la asociación persistió), método GET, URL {{baseUrl}}/artistas/{{artistaId}}/albumes.
  2. 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")));
});
  1. Haz clic en Send y confirma los ✅.
  1. Crea la petición 12 POST asociar de nuevo (400), método POST, URL {{baseUrl}}/artistas/{{artistaId}}/albumes/{{albumId}}.
  2. 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");
});
  1. 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»
  1. Crea la petición 13 POST asociar un álbum inexistente (404), método POST, URL {{baseUrl}}/artistas/{{artistaId}}/albumes/999999.
  2. 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");
});
  1. 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.

  1. Crea la petición 14 PUT artistas/{id} conserva los álbumes, método PUT, URL {{baseUrl}}/artistas/{{artistaId}}. Ya asociaste un álbum a este artista en la petición 10.
  2. En Body (raw, JSON):
{
"nombre": "{{nombreEditado}}",
"paisOrigen": "Ecuador"
}
  1. 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.

  1. 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»
  1. Crea la petición 15 DELETE artistas/{id}/albumes/{albumId} (desasociar), método DELETE, URL {{baseUrl}}/artistas/{{artistaId}}/albumes/{{albumId}}.
  2. 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(""));
  1. 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á»
  1. Crea la petición 16 GET artistas/{id}/albumes (ya no está), método GET, URL {{baseUrl}}/artistas/{{artistaId}}/albumes.
  2. 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);
  1. Haz clic en Send y confirma los ✅.
  1. Crea la petición 17 DELETE desasociar de nuevo (400), método DELETE, URL {{baseUrl}}/artistas/{{artistaId}}/albumes/{{albumId}}.
  2. 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");
});
  1. Haz clic en Send y confirma los ✅.

  1. Crea la petición 18 DELETE artistas/{id}, método DELETE, URL {{baseUrl}}/artistas/{{artistaId}}.
  2. 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(""));
  1. Haz clic en Send y confirma los ✅.

Un DELETE que responde 204 no demuestra que el recurso se borró. Esta petición lo comprueba, y además deja el Environment limpio.

  1. Crea la petición 19 GET artistas/{id} tras borrar (404), método GET, URL {{baseUrl}}/artistas/{{artistaId}}.
  2. 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");
  1. 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.


Hasta ahora enviaste las peticiones una por una. Ahora corre las 19 juntas, en orden.

  1. Haz clic derecho sobre la Collection (o en sus tres puntos) → Run.
  2. Selecciona tu Environment y confirma que las 19 peticiones de este tutorial están marcadas y en orden (más la GET artistas del Release 2, si la dejaste).
  3. 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.

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:

Ventana de terminal
newman run collections/Artista.postman_collection.json -e collections/environment.postman_environment.json

Newman imprime cada petición con sus tests y, al final, una tabla con el total de peticiones y de aserciones.


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.


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 ApiError y 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.