¿Qué es la librería de aserciones Chai?

Chai HTTP: Pruebas de API Robustas en Node.js

28/06/2024

Valoración: 4.54 (2629 votos)

En el dinámico mundo del desarrollo de software, la creación de APIs robustas y fiables es fundamental. Sin embargo, una API no está completa sin un conjunto exhaustivo de pruebas que garanticen su correcto funcionamiento bajo diversas condiciones. Aquí es donde entran en juego herramientas especializadas que nos permiten simular interacciones reales y verificar el comportamiento esperado. Este artículo profundiza en cómo utilizar el poder combinado de Mocha, Chai y, crucialmente, Chai HTTP, para realizar pruebas de integración efectivas en tus APIs desarrolladas con Node.js.

¿Cómo lanzar una llamada a la librería de Chai?
let chai = require ('chai'); let chaiHttp = require ('chai-http'); const expect = require ('chai').expect; Una vez que tenemos los paquetes requeridos, tenemos que decirle a Chai que utilice la librería de Chai HTTP y definimos la url donde vamos a lanzar las llamadas a la API.

Entendiendo el Ecosistema de Pruebas: Mocha, Chai y Chai HTTP

Antes de sumergirnos en los detalles de la implementación, es vital comprender el papel de cada una de estas herramientas en nuestro flujo de trabajo de pruebas. Aunque ya hemos abordado brevemente este tema en otras ocasiones, un repaso rápido nos permitirá ponernos en contexto y apreciar la sinergia que existe entre ellas.

Mocha: El Framework de Pruebas Robusto

Mocha es un framework de pruebas altamente flexible y rico en características para Node.js y el navegador. Su diseño permite realizar pruebas asíncronas de manera sencilla y ofrece una gran variedad de interfaces de informes, lo que facilita la visualización y el análisis de los resultados. Con Mocha, puedes estructurar tus pruebas de forma clara utilizando bloques describe para agrupar conjuntos de tests y bloques it para definir pruebas individuales. Es la columna vertebral que orquesta la ejecución de nuestros escenarios de prueba, proporcionando un entorno limpio y predecible para cada test.

Chai: La Librería de Aserciones Versátil

Chai es una poderosa librería de aserciones compatible con los enfoques BDD (Behavior Driven Development) y TDD (Test Driven Development). Su principal función es permitirnos escribir afirmaciones, o aserciones, sobre el estado de nuestras aplicaciones o el valor de las variables. En otras palabras, con Chai podemos expresar lo que esperamos que suceda en una prueba. Ofrece varias interfaces para escribir estas aserciones, como expect (más legible y expresiva, ideal para BDD) y should, lo que la hace adaptable a diferentes estilos de codificación. Chai se empareja armónicamente con cualquier framework de pruebas JavaScript, incluyendo Mocha, para validar los resultados de nuestras operaciones.

Chai HTTP: La Extensión Clave para Pruebas de API

Aquí es donde la magia de las pruebas de API se materializa. Chai HTTP es una extensión específica de la librería Chai diseñada para simplificar la realización de peticiones HTTP en un entorno de pruebas. Permite simular llamadas a tu API (GET, POST, PUT, DELETE, PATCH, etc.) y luego utilizar las potentes aserciones de Chai para validar las respuestas. Esto es crucial para las pruebas de integración, ya que nos permite verificar que todos los componentes de la API (enrutamiento, controladores, bases de datos, etc.) funcionan correctamente en conjunto, tal como lo harían en un escenario real.

Configuración Inicial para tus Pruebas de API

Para comenzar a utilizar Chai HTTP, primero necesitamos instalar las dependencias necesarias en nuestro proyecto Node.js. Esto generalmente se hace a través de npm (Node Package Manager). Una vez instaladas, el primer paso en nuestros archivos de prueba será requerir estas librerías y configurar Chai para que utilice la extensión HTTP.

Aquí tienes el código de configuración inicial que necesitarás en tus archivos de prueba:

let chai = require('chai'); let chaiHttp = require('chai-http'); const expect = require('chai').expect; // Le decimos a Chai que use la librería Chai HTTP chai.use(chaiHttp); // Definimos la URL base de nuestra API const url = 'http://localhost:3000'; // Asegúrate de que tu API esté corriendo en este puerto

En este fragmento, importamos chai, chai-http y la interfaz expect de chai. Luego, con chai.use(chaiHttp), extendemos las capacidades de Chai para incluir los métodos de petición HTTP. Finalmente, definimos la URL de nuestra API, que será el objetivo de todas nuestras solicitudes de prueba. Es fundamental que tu servidor API esté ejecutándose en la URL especificada para que las pruebas puedan interactuar con él.

Realizando Peticiones HTTP con Chai HTTP: Ejemplos Prácticos

Ahora, veamos cómo podemos aplicar Chai HTTP para probar las principales operaciones HTTP en una API RESTful. Utilizaremos ejemplos basados en la gestión de una lista de países, cada uno con un ID, nombre, año y número de días.

Peticiones POST: Creando Recursos

Las peticiones POST se utilizan para enviar datos a un servidor con el fin de crear un nuevo recurso. A menudo, necesitamos verificar tanto la creación exitosa como el manejo de errores cuando los datos enviados son incorrectos.

Ejemplo 1: POST Exitoso

Este test simula la inserción de un nuevo país en la API. Enviamos un objeto JSON con los datos del país y esperamos una respuesta exitosa (código de estado 200).

describe('Insert a country: ',()=>{ it('should insert a country', (done) => { chai.request(url) .post('/country') .send({id:0, country: "Croacia", year: 2017, days: 10}) .end( function(err,res){ console.log(res.body) expect(res).to.have.status(200); done(); }); }); });

Aquí, chai.request(url) inicia la petición a nuestra API. Luego, .post('/country') especifica el método HTTP y la ruta. .send({...}) es donde proporcionamos el cuerpo de la petición en formato JSON. Finalmente, .end() ejecuta la petición y nos permite acceder a la respuesta (res). Dentro de la función de callback, expect(res).to.have.status(200) es nuestra aserción clave, verificando que la API respondió con un estado 200 OK. La llamada a done() es crucial en Mocha para indicar que una prueba asíncrona ha finalizado.

Ejemplo 2: POST con Error

Este test verifica cómo la API maneja un intento de inserción con datos inválidos, esperando una respuesta de error (código de estado 500).

describe('Insert a country with error: ',()=>{ it('should receive an error', (done) => { chai.request(url) .post('/country') .send({id:1, country: "Madrid", year: 2010, days: 10}) // "Madrid" no es un país .end( function(err,res){ console.log(res.body) expect(res).to.have.status(500); done(); }); }); });

Este ejemplo es similar al anterior, pero en lugar de esperar un 200, la aserciónexpect(res).to.have.status(500) valida que la API responde con un error interno del servidor, lo que indica un manejo adecuado de datos inesperados.

Peticiones GET: Obteniendo Información

Las peticiones GET se utilizan para recuperar recursos del servidor. Podemos obtener una lista completa o un recurso específico por su identificador.

Ejemplo 3: GET de Todos los Países

Este test recupera todos los países de la API y verifica que la petición fue exitosa.

describe('get all countries: ',()=>{ it('should get all countries', (done) => { chai.request(url) .get('/countries') .end( function(err,res){ console.log(res.body) expect(res).to.have.status(200); done(); }); }); });

Ejemplo 4: GET de un País Específico por ID

Aquí, obtenemos un país en particular utilizando su ID y verificamos tanto el estado de la respuesta como una propiedad específica del objeto devuelto.

¿Qué es la librería de aserciones Chai?
describe('get the country with id 1: ',()=>{ it('should get the country with id 1', (done) => { chai.request(url) .get('/country/1') .end( function(err,res){ console.log(res.body) expect(res.body).to.have.property('id').to.be.equal(1); expect(res).to.have.status(200); done(); }); }); });

La aserciónexpect(res.body).to.have.property('id').to.be.equal(1) es un excelente ejemplo de cómo Chai permite encadenar aserciones para realizar verificaciones complejas sobre el cuerpo de la respuesta, asegurándonos de que no solo recibimos un objeto, sino que este contiene los datos correctos.

Peticiones PUT: Actualizando Datos

Las peticiones PUT se emplean para actualizar recursos existentes en el servidor. En este caso, modificaremos un atributo de un país.

Ejemplo 5: Actualizar Días de un País

describe('update the days of country with id 1: ',()=>{ it('should update the number of days', (done) => { chai.request(url) .put('/country/1/days/20') .end( function(err,res){ console.log(res.body) expect(res.body).to.have.property('days').to.be.equal(20); expect(res).to.have.status(200); done(); }); }); });

Similar a las peticiones POST, .put() se utiliza para especificar el método de actualización. La aserción verifica que la propiedad 'days' del país actualizado ahora tiene el nuevo valor de 20.

Peticiones DELETE: Eliminando Recursos

Las peticiones DELETE se utilizan para eliminar recursos del servidor. Es común realizar verificaciones previas y posteriores a la eliminación para asegurar que el recurso fue realmente removido.

Ejemplo 6: Eliminar un País por ID

describe('delete the country with id 1: ',()=>{ it('should delete the country with id 1', (done) => { chai.request(url) .get('/countries') // Primero, obtener todos los países para verificar su número .end( function(err,res){ console.log(res.body) expect(res.body).to.have.lengthOf(2); // Asumiendo que hay 2 países inicialmente expect(res).to.have.status(200); chai.request(url) .del('/country/1') // Luego, eliminar el país con ID 1 .end( function(err,res){ console.log(res.body) expect(res).to.have.status(200); chai.request(url) .get('/countries') // Finalmente, obtener los países de nuevo para verificar la eliminación .end( function(err,res){ console.log(res.body) expect(res.body).to.have.lengthOf(1); // Ahora debe haber solo 1 país expect(res.body[0]).to.have.property('id').to.be.equal(0); // Y debe ser el país restante expect(res).to.have.status(200); done(); }); }); }); }); });

Este test encadena múltiples peticiones para simular un flujo completo: verificar la existencia, eliminar y luego verificar la ausencia. La aserciónexpect(res.body).to.have.lengthOf() es muy útil para verificar el número de elementos en una lista.

Envío de Datos en Formato de Formulario (FORM)

A veces, una API espera datos enviados como un formulario HTML tradicional en lugar de JSON. Chai HTTP permite especificar el tipo de contenido de la petición.

Ejemplo 7: POST con Datos de Formulario

describe('Insert a country with a form: ',()=>{ it('should receive an error because we send the country in form format', (done) => { chai.request(url) .post('/country') .type('form') // Indicamos que el tipo de contenido es 'application/x-www-form-urlencoded' .send({id:0, country: "Croacia", year: 2017, days: 10}) .end( function(err,res){ console.log(res.body) expect(res).to.have.status(500); done(); }); }); });

Al utilizar .type('form'), Chai HTTP ajusta la cabecera Content-Type de la petición. En este ejemplo, si la API espera JSON, el servidor responderá con un error, demostrando la importancia de enviar los datos en el formato correcto.

Gestión de Sesiones y Cookies con Agent

Para probar flujos que involucran autenticación y manejo de sesiones (como el uso de cookies), Chai HTTP ofrece la funcionalidad de 'agent'. Un 'agent' es una instancia de cliente HTTP que persiste las cookies y otra información de sesión a lo largo de múltiples peticiones.

Ejemplo 8: Autenticación y Uso de Cookies

var agent = chai.request.agent(url) // Creamos un agent para la URL base describe('Authenticate a user: ',()=>{ it('should receive an OK and a cookie with the authentication token', (done) => { agent // Usamos el agent para la petición .get('/authentication') .auth('user', 'password') // Realizamos una autenticación básica .end( function(err,res){ console.log(res.body) expect(res).to.have.cookie('authToken'); // Verificamos que se recibió la cookie expect(res).to.have.status(200); // Luego, usamos el mismo agent para otra petición que requiere la cookie return agent.get('/personalData/user') .then(function (res) { expect(res).to.have.status(200); console.log(res.body) done(); }); // done(); // Este done() extra podría causar un error en Mocha, el done() dentro del then es suficiente }); }); }); describe('Obtain personal data without authToken: ',()=>{ it('should receive an error because we need authToken', (done) => { // Aquí, usamos un nuevo request sin agent o un agent sin autenticar // para demostrar que sin la cookie, la petición falla. // Nota: Si el 'agent' anterior persiste la cookie globalmente, este test podría fallar. // Para un test aislado, se debería crear un nuevo chai.request(url) o resetear el agent. // En este caso, el agent ya tiene la cookie del test anterior, por lo que este test // está diseñado para demostrar que la cookie no se 'limpia' automáticamente entre 'describe's // a menos que se redefina el agent o se use chai.request(url) directamente. chai.request(url) // Usamos chai.request(url) directamente para no usar el agent autenticado .get('/personalData/user') .then(function (res) { expect(res).to.have.status(500); console.log(res.body) done(); // Asegúrate de llamar a done() aquí }) .catch(function(err) { // Manejar posibles errores de la petición expect(err).to.have.status(500); console.log(err.response.body); done(); }); }); });

Un agent es crucial para simular flujos de usuario completos, donde las cookies o tokens de sesión son necesarios para mantener el estado. La aserciónexpect(res).to.have.cookie('authToken') permite verificar si una cookie específica fue establecida en la respuesta. Es importante notar que un agent mantiene su estado (incluyendo cookies) a lo largo de las peticiones que se hacen a través de él dentro de un mismo contexto de prueba. Si necesitas un test completamente aislado, deberías usar chai.request(url) directamente sin un agent, o crear un nuevo agent para cada test.

Funcionalidades Adicionales de Chai HTTP

Además de los métodos de petición básicos, Chai HTTP ofrece una serie de funcionalidades que enriquecen aún más tus pruebas de integración:

  • .set('Header-Name', 'value'): Permite añadir cabeceras HTTP personalizadas a tus peticiones, ideal para tokens de autorización, API keys, etc.
  • .query({param: 'value'}): Adjunta parámetros de consulta a la URL de la petición, útil para filtrar o paginar recursos.
  • .attach('fieldName', fs.readFileSync('path/to/file'), 'filename.ext'): Facilita la subida de archivos en peticiones multipart/form-data.
  • .redirects(number): Controla cuántas redirecciones debe seguir la petición automáticamente.

Aserciones Exclusivas de Chai HTTP

Chai HTTP extiende las capacidades de Chai con aserciones específicas para respuestas HTTP, que se pueden combinar con las aserciones estándar de Chai:

  • expect(res).to.have.status(code): Ya vista, verifica el código de estado HTTP (ej. 200, 404, 500).
  • expect(res).to.have.header('header-name', 'value'): Comprueba si una cabecera específica está presente en la respuesta y opcionalmente su valor.
  • expect(res).to.have.cookie('cookie-name', 'value'): Verifica la presencia de una cookie específica en la respuesta y opcionalmente su valor.
  • expect(res).to.be.json, expect(res).to.be.html, expect(res).to.be.text: Aserciones para verificar el tipo de contenido (Content-Type) de la respuesta.
  • expect(res).to.redirectTo('/expected/path'): Verifica si la respuesta es una redirección a una URL específica.

Ejecución de las Pruebas

Para ejecutar los tests, primero asegúrate de tener Node.js instalado. Luego, en la carpeta de tu proyecto (donde también debería estar tu API):

  1. Instala las dependencias: npm install
  2. Inicia tu servidor API (si no está ya corriendo): node server.js (asumiendo que tu archivo principal de la API se llama server.js y escucha en el puerto 3000).
  3. Ejecuta los tests con Mocha: mocha test/*.js --timeout 15000 (el --timeout es útil para pruebas de integración que pueden tardar más).

Tabla Comparativa: Roles en el Ecosistema de Pruebas

Para clarificar aún más la función de cada herramienta, aquí te presentamos una tabla resumen:

HerramientaFunción PrincipalEnfoque
MochaFramework de pruebas: Organiza, ejecuta y reporta los tests. Proporciona la estructura y el flujo.Unitaria, Integración, End-to-End
ChaiLibrería de aserciones: Permite escribir afirmaciones legibles sobre los resultados esperados de las pruebas.BDD/TDD
Chai HTTPExtensión para Chai: Facilita la realización de peticiones HTTP a APIs en el contexto de las pruebas.Pruebas de integración de API

Preguntas Frecuentes (FAQ) sobre Chai y Pruebas de API

¿Por qué se recomiendan Mocha y Chai juntos?

Mocha proporciona el marco de trabajo para organizar y ejecutar tus tests, ofreciendo ganchos (before, after, beforeEach, afterEach) y una excelente capacidad de reporte. Chai, por otro lado, te da el lenguaje para expresar tus expectativas sobre los resultados de esos tests. Juntos, forman una combinación potente y complementaria: Mocha ejecuta el escenario y Chai valida que el comportamiento es el esperado. Son herramientas muy populares y bien documentadas, lo que facilita encontrar soporte y ejemplos en la comunidad.

¿Es Chai HTTP exclusivo para Node.js?

Sí, Chai HTTP está diseñado específicamente para ser utilizado en entornos Node.js, ya que se basa en las capacidades de red de Node.js para realizar las peticiones HTTP. Si bien Chai (la librería de aserciones base) puede usarse tanto en Node.js como en el navegador, Chai HTTP es para pruebas de backend donde tu API está corriendo en un servidor Node.js.

¿Existen alternativas a Chai HTTP para pruebas de API?

Absolutamente. El ecosistema de pruebas en JavaScript es vasto. Algunas alternativas populares para realizar pruebas de integración de API incluyen: Supertest (a menudo usado con Express.js y Mocha), Jest (un framework de pruebas todo en uno que también puede hacer peticiones HTTP con librerías como Axios o fetch), o incluso librerías de petición HTTP genéricas como Axios o Node-fetch combinadas con cualquier librería de aserciones. La elección a menudo depende de las preferencias del equipo, la complejidad de la API y el ecosistema de herramientas ya en uso.

¿Cómo se manejan los errores en las pruebas con Chai?

El manejo de errores en las pruebas con Chai y Chai HTTP se realiza principalmente a través de la aserción del código de estado HTTP esperado. Como vimos en los ejemplos POST con error, puedes esperar un status(500) o status(400) si anticipas un error del servidor o un error de validación del cliente, respectivamente. Además, puedes inspeccionar el res.body para verificar mensajes de error específicos o estructuras de error definidas por tu API. Para peticiones asíncronas que pueden lanzar excepciones, es común usar bloques try...catch o el manejo de promesas (.then().catch()) junto con aserciones para verificar que la excepción esperada fue lanzada o que la promesa fue rechazada correctamente.

Conclusión

La combinación de Mocha, Chai y Chai HTTP ofrece un conjunto de herramientas extremadamente potente y flexible para llevar a cabo pruebas de integración exhaustivas en tus APIs Node.js. Su compatibilidad, facilidad de uso y la gran cantidad de ejemplos y soporte en la comunidad los convierten en una opción altamente recomendable para cualquier desarrollador que busque asegurar la calidad y fiabilidad de sus servicios web. Al dominar estas librerías, no solo validarás el comportamiento de tu API, sino que también mejorarás la confianza en tu código y agilizarás el ciclo de desarrollo. Te animamos a explorar estas herramientas, experimentar con sus diversas funcionalidades y adaptarlas a las necesidades específicas de tus proyectos. La inversión en pruebas de integración robustas siempre rinde frutos en forma de software más estable y mantenible.

Si quieres conocer otros artículos parecidos a Chai HTTP: Pruebas de API Robustas en Node.js puedes visitar la categoría Librerías.

Subir