Probar un recurso de principio a fin
Crear, leer, actualizar y borrar, y comprobar que cada operación ocurrió de verdad.
La mayoría de las APIs se construyen alrededor de recursos: libros, pedidos, usuarios, facturas. Para cada recurso suele haber una forma de crearlo, leerlo, cambiarlo y borrarlo. Quienes prueban lo llaman CRUD (create, read, update, delete), y un test CRUD sigue a un registro durante toda su vida. Es lo primero que deberías probar en cualquier recurso nuevo, porque casi todas las demás funcionalidades dependen de que funcione.
Esta lección usa /api/Booksinicia sesión para verlo. Admite todas las operaciones, y su página de referencia enumera los campos. Los métodos ya los conoces de Métodos HTTP. Aquí la pregunta es otra: ¿cómo demuestras que cada operación hizo lo que decía?
Una respuesta es una afirmación, no una prueba
Cuando un servidor responde a un POST con 201 y una copia del registro, te está diciendo lo que cree que guardó. Esa copia suele construirse a partir del mismo objeto que estaba a punto de guardarse, así que puede parecer perfecta aunque el guardado fallara, o aunque un campo se perdiera de camino a la base de datos. La única forma de saber qué se guardó es volver a pedirlo en una petición aparte.
Así que un test CRUD alterna entre hacer algo y comprobarlo con una lectura nueva:
- Crea un registro. Comprueba la respuesta.
- Léelo por ID. Compara cada campo con lo que enviaste.
- Búscalo en el endpoint de lista.
- Sustitúyelo con PUT. Vuelve a leerlo.
- Cambia un campo con PATCH. Vuelve a leerlo.
- Bórralo. Vuelve a leerlo y espera un 404.
- Bórralo otra vez y mira qué pasa.
Crear
curl -i -X POST https://api.testingapis.com/api/Books \
-H "Content-Type: application/json" \
-d '{
"title": "Testing Web APIs",
"author": "Mark Winteringham",
"isbn": "9781617299537",
"genre": "testing",
"publishedYear": 2022,
"pages": 264,
"language": "English",
"price": 49.99,
"rating": 4.6,
"available": true,
"tags": ["api", "testing"]
}'HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{
"message": "Book created successfully",
"data": {
"id": 26,
"title": "Testing Web APIs",
"author": "Mark Winteringham",
"isbn": "9781617299537",
"genre": "testing",
"publishedYear": 2022,
"pages": 264,
"language": "English",
"price": 49.99,
"rating": 4.6,
"available": true,
"tags": ["api", "testing"]
}
}Qué comprobar en esta respuesta, más o menos por orden de importancia:
- El estado es 201, no 200. Los dos significan éxito, pero 201 dice que existe un recurso nuevo, y los clientes a veces toman decisiones según eso.
- Hay un
id, y es un número (o el tipo que prometa la documentación). Guárdalo. Lo necesitarán todos los pasos siguientes. - Cada campo que enviaste vuelve con el mismo valor. Compáralos uno a uno, también los que parecen aburridos. Un precio de
49.99que vuelve como49.9899999o"49.99"es un bug. - Si una cabecera
Locationapunta al registro nuevo. Muchas APIs REST la envían con un 201. Esta no. Es una decisión de diseño más que un defecto, pero deberías saber qué hace tu API y reflejarlo en tus tests.
Ahora mira qué pasa con un pequeño error en el cuerpo. El campo se llama publishedYear, y aquí se envía como year:
curl -X POST https://api.testingapis.com/api/Books \
-H "Content-Type: application/json" \
-d '{"title": "The Test Book", "author": "Ada Tester", "year": 2026}'{
"message": "Book created successfully",
"data": {
"id": 26,
"title": "The Test Book",
"author": "Ada Tester",
"isbn": "",
"genre": "",
"publishedYear": 0,
"pages": 0,
"language": "",
"price": 0,
"rating": 0,
"available": false,
"tags": []
}
}El servidor aceptó la petición, ignoró year sin decir nada y guardó 0como año. Nada en el código de estado te dice que algo fue mal. Solo lo pillas comparando el registro guardado con lo que querías enviar. Los campos ignorados en silencio son una de las fuentes más comunes de bugs del tipo "los datos están mal y nadie sabe por qué". Algunas APIs rechazan los campos desconocidos; la siguiente lección lo trata.
Campos que rellena el servidor
Algunos campos no vienen de ti: el ID y, en muchos recursos, marcas de tiempo como createdAt o updatedAt. No puedes compararlos con un valor fijo, porque cambian en cada ejecución. Prueba sus propiedades en su lugar:
- El ID está, tiene el tipo correcto y es distinto de los IDs que creaste antes.
- Un
createdAtes una fecha válida en el formato documentado y cae a pocos segundos de cuando enviaste la petición. Ten en cuenta las diferencias de reloj entre tu máquina y el servidor. - Un
updatedAtcambia tras una actualización ycreatedAtno. - Enviar tu propio
iden el cuerpo no te permite elegirlo. La documentación de Books dice que un ID en el cuerpo se ignora. Pruébalo con"id": 1y comprueba después que el libro 1 sigue intacto.
Volver a leerlo
Recupera el registro con el ID que te dieron y compara el cuerpo entero con lo que enviaste. Luego mira la lista. En esta API puedes buscar por título:
/api/Books?q=web%20apisSi tu libro no está en los resultados, la respuesta de creación mintió o la lista lee de un sitio distinto al del endpoint de un solo registro (una caché, un índice de búsqueda, una réplica de lectura). Todo eso pasa en sistemas reales. Mirar la lista también detecta un problema más discreto: el registro se guarda pero se filtra fuera de las listas, por ejemplo porque un filtro por defecto oculta los registros con un campo vacío.
Actualizar con PUT y PATCH
PUT sustituye el registro entero. Lo que omitas se reinicia, normalmente a un valor vacío o por defecto. La gente lo olvida continuamente, tanto en el cliente como en el servidor, así que pruébalo a propósito. Aquí se actualiza un libro con solo tres campos:
curl -X PUT https://api.testingapis.com/api/Books/27 \
-H "Content-Type: application/json" \
-d '{"title": "Lessons Learned in Software Testing", "author": "Cem Kaner", "price": 39.0}'{
"message": "Book updated successfully",
"data": {
"id": 27,
"title": "Lessons Learned in Software Testing",
"author": "Cem Kaner",
"isbn": "",
"genre": "",
"publishedYear": 0,
"pages": 0,
"language": "",
"price": 39.0,
"rating": 0,
"available": false,
"tags": []
}
}El ISBN, el número de páginas, el año y las etiquetas han desaparecido. Es el comportamiento correcto de PUT. Si el servidor hubiera conservado los valores antiguos, se estaría comportando como PATCH, y un cliente que dependiera de PUT para vaciar un campo nunca lo conseguiría. La misma petición envió también "id": 1 en el cuerpo; el registro mantuvo el ID 27, y un GET aparte confirmó que el libro 1 no había cambiado.
Un PUT al que le falta un campo obligatorio falla con un 400, como debe: {"price": 35} solo devuelve Missing required field: title.
PATCH cambia solo los campos que envías. Esta API usa JSON merge patch, y su respuesta enumera qué cambió y qué ignoró:
curl -X PATCH https://api.testingapis.com/api/Books/26 \
-H "Content-Type: application/merge-patch+json" \
-d '{"price": 35, "available": false, "colour": "blue", "id": 999}'{
"message": "Book patched successfully",
"changed": ["price", "available"],
"ignored": ["colour", "id"],
"data": {
"id": 26,
"title": "Testing Web APIs",
"author": "Mark Winteringham",
"isbn": "9781617299537",
"genre": "testing",
"publishedYear": 2022,
"pages": 264,
"language": "English",
"price": 35,
"rating": 4.6,
"available": false,
"tags": ["api", "testing"]
}
}Comprueba que los dos campos nombrados cambiaron, que todos los demás conservaron su valor anterior y que el id ignorado de verdad se ignoró. Luego haz un GET nuevo y vuelve a comprobarlo todo. Un PATCH que actualiza la respuesta pero no el registro guardado es raro, pero cuando pasa es justo el tipo de bug que llega a producción, porque el cliente muestra el valor nuevo hasta que se recarga la página.
Borrar, y volver a borrar
$ curl -i -X DELETE https://api.testingapis.com/api/Books/26
HTTP/1.1 200 OK
{"message":"Book deleted successfully"}
$ curl -i https://api.testingapis.com/api/Books/26
HTTP/1.1 404 Not Found
{"status":404,"error":"Not Found","message":"Book with ID 26 does not exist."}
$ curl -i -X DELETE https://api.testingapis.com/api/Books/26
HTTP/1.1 404 Not Found
{"status":404,"error":"Not Found","message":"Book with ID 26 does not exist."}Después de un borrado, un GET tiene que devolver 404. Comprueba también que el registro ha desaparecido de la lista y que el contador bajó (/api/Books/countinicia sesión para verlo devuelve {"count": 25} con los datos iniciales).
El segundo DELETE es donde las APIs difieren. Esta devuelve 404. Otras devuelven 204 o 200 en cada borrado, con el argumento de que el estado final (no existe ese registro) es el mismo. Las dos opciones se pueden defender. Lo que buscas es un 500, o un 200 que dice haber borrado algo que no estaba. Envía también PUT y PATCH al ID borrado. Aquí PATCH recibe un 404, pero un PUT con un cuerpo incompleto recibe un 400 por el campo que falta antes de que el servidor note que el registro no existe. Qué error gana cuando se aplican dos merece una frase en la documentación, y a menudo no la tiene.
Crea tus propios datos
Tienta probar contra el registro 1 porque siempre está. No lo hagas. Cualquiera puede cambiar o borrar el libro 1 en un servidor compartido, y un test que lo edita rompe todos los demás tests que lo leen. Cada test debería crear los registros que necesita, trabajar solo con los IDs que le dieron y borrarlos al final.
Esta API es un buen sitio para ver por qué. Guarda sus datos en memoria y la comparte todo el que la usa. Mientras se escribía esta lección, otra persona creó un libro con ID 26 en los pocos segundos entre uno de nuestros borrados y la siguiente creación. Un test que diera por hecho que "el siguiente libro será el 26" habría fallado por razones que no tenían nada que ver con el código probado.
El mismo experimento mostró otra cosa. Después de borrar el libro 26, el siguiente libro creado volvió a recibir el ID 26. Este servidor reparte el siguiente número libre, así que los IDs se reutilizan. Si otro sistema guardó "libro 26" antes del borrado, ahora apunta a otro libro. Que sea un bug depende del producto, pero es el tipo de hallazgo que vale la pena comentar. Por esta razón, la mayoría de las bases de datos nunca reutilizan IDs.
Endpoints masivos
Muchas APIs permiten crear o borrar varios registros en una petición. La pregunta interesante es qué pasa cuando algunos elementos son malos.
curl -X POST https://api.testingapis.com/api/Books/bulk \
-H "Content-Type: application/json" \
-d '[
{"title": "Bulk A", "author": "Tester"},
{"title": "Bulk B", "author": "Tester"},
{"title": "", "author": "Tester"}
]'
{"status":400,"error":"Bad Request","message":"Item [2]: Missing required field: title"}El tercer elemento tiene el título vacío, así que la petición entera falla y el error nombra el elemento por su índice. La documentación de Books dice que en ese caso no se guarda nada, y deberías verificarlo: comprueba que "Bulk A" no existe después de la petición fallida. Una API que guarda los dos primeros elementos y luego falla deja al cliente sin idea de qué reintentar.
El borrado masivo informa de qué IDs encontró y cuáles no:
curl -X DELETE "https://api.testingapis.com/api/Books/bulk?ids=27,28,5000"
{"message":"2 book record(s) deleted","deleted":[27,28],"notFound":[5000]}| Caso masivo | Qué comprobar |
|---|---|
| Todos los elementos válidos | Se guarda cada elemento, los IDs son distintos, el contador sube tanto como los enviados. |
| Un elemento no válido | No se guarda nada, o la respuesta dice exactamente qué elementos se guardaron. |
| Array vacío | Un 400 claro, no un 201 que no creó nada. |
| Más del límite (100 aquí) | Un 400 que indique el límite. |
| IDs duplicados en un borrado masivo | Cada registro se borra una vez, sin error por la segunda aparición. |
Ejercicio
Recorre el ciclo de vida completo en /api/Moviesinicia sesión para verlo, usando su página de referencia para los campos. Crea una película con todos los campos rellenos y luego:
- Vuelve a leerla y compara cada campo con lo que enviaste.
- Encuéntrala con
?q=y una palabra de su título. - Haz PUT solo con los campos obligatorios. ¿Qué campos se reiniciaron?
- Haz PATCH de un campo y envía un nombre de campo que no existe. Lee la lista
ignored. - Bórrala, y luego haz GET, PATCH y DELETE otra vez. Apunta cada estado.
Termina apuntando una cosa que preguntarías a los desarrolladores (un campo ignorado, un ID reutilizado, un error que se impuso a otro). Decidir qué informar es tan parte del test como las peticiones.