Node
Tutorial Node: Testing en Node.js
Aprende a crear tests en Node.js sin librerías, usando aserciones nativas y testing unitario para mejorar la calidad de tu código.
Aprende Node y certifícateCreación de test en nodejs sin librerías
Para iniciar una estrategia de pruebas sin depender de bibliotecas externas, se puede crear un script de JavaScript que invoque las funciones a examinar y verifique manualmente sus resultados. Esta técnica implica ejecutar el script con Node.js y comprobar si se producen errores en la salida. Al no requerir librerías externas, se ofrece un método muy básico de evaluación, aunque suficiente para proyectos pequeños o de demostración.
Un ejemplo sencillo consiste en exportar una función desde un módulo para luego importarla en el script de pruebas y, mediante comprobaciones con bloques if
, determinar si la función produce la respuesta esperada. En caso negativo, se lanza un error que detenga la ejecución.
Consideremos que tenemos las funciones de autenticación que hemos desarrollado en lecciones anteriores:
// archivo auth.js
import { hash, compare } from "bcrypt";
export async function hashPassword(password) {
return await hash(password, 10);
}
export async function verifyPassword(password, hashedPassword) {
return await compare(password, hashedPassword);
}
export function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
Archivo test.mjs para creación de tests que prueben las funciones del archivo anterior.
// archivo test.js
import { hashPassword, verifyPassword, validateEmail } from "./auth.js";
async function testHashPassword() {
const password = "MySecurePassword123";
const hash = await hashPassword(password);
if (!hash || hash === password || hash.length < 50) {
throw new Error("La función de hash no funciona correctamente");
}
console.log("✓ Test de hash de contraseña superado");
}
async function testPasswordVerification() {
const password = "TestPassword123";
const hash = await hashPassword(password);
const isValid = await verifyPassword(password, hash);
if (!isValid) {
throw new Error("La verificación de contraseña falló");
}
console.log("✓ Test de verificación de contraseña superado");
}
function testEmailValidation() {
const validEmail = "usuario@ejemplo.com";
const invalidEmail = "email-invalido";
if (!validateEmail(validEmail)) {
throw new Error("Email válido fue rechazado");
}
if (validateEmail(invalidEmail)) {
throw new Error("Email inválido fue aceptado");
}
console.log("✓ Test de validación de email superado");
}
async function runAllTests() {
try {
await testHashPassword();
await testPasswordVerification();
testEmailValidation();
console.log("Todos los tests han finalizado exitosamente");
} catch (error) {
console.error("Test falló:", error.message);
process.exit(1);
}
}
runAllTests();
En el ejemplo anterior, se lanza un error con throw
si el resultado no coincide con el valor esperado. De este modo, se controla manualmente la ejecución y se informa al desarrollador en la consola cuando el test ha fallado o ha pasado con éxito. Usar esta aproximación requiere disciplina para ir añadiendo más comprobaciones de manera organizada y, a medida que el proyecto crezca, podría complementarse con técnicas adicionales para abarcar más casos. De todas formas, este sistema ofrece un punto de partida fundamental para entender la mecánica básica de las pruebas en Node.js sin apoyarse en otras dependencias.
Aserciones nativas de nodejs
El módulo assert de Node.js ofrece un conjunto de métodos específicos para verificar valores en los test. Utiliza una sintaxis directa y permite lanzar excepciones si algo no coincide con la expectativa, lo que facilita la identificación de fallos de inmediato. Se importa mediante require
en entornos CommonJS y también puede usarse junto con el estándar ES Modules.
Para usar assert, basta con llamar a métodos como strictEqual
o deepStrictEqual
dentro de tus pruebas. Estas funciones comparan dos valores y lanzan un error en caso de discrepancia. Un ejemplo básico puede verse en este snippet:
// archivo test-auth.js
import { hashPassword, verifyPassword, validateEmail } from "./auth.js";
import { strictEqual, notStrictEqual, rejects } from "assert";
// Test de validación de email
const validEmail = validateEmail("test@ejemplo.com");
strictEqual(validEmail, true, "Email válido debería ser aceptado");
const invalidEmail = validateEmail("email-sin-formato-valido");
strictEqual(invalidEmail, false, "Email inválido debería ser rechazado");
console.log("✓ Tests de validación de email superados");
// Test de hash de contraseña
async function testPasswordHashing() {
const password = "MiContraseñaSegura";
const hash = await hashPassword(password);
notStrictEqual(hash, password, "El hash debe ser diferente a la contraseña original");
strictEqual(typeof hash, "string", "El hash debe ser una cadena");
console.log("✓ Test de hash con aserciones superado");
}
testPasswordHashing();
Si tenemos middleware de autorización de lecciones anteriores, también podemos testear su comportamiento:
// Test de middleware de roles
import { verifyRole } from "./middleware/authorization.js";
function testRoleMiddleware() {
let accessGranted = false;
let responseStatus = null;
// Simulamos objetos req, res, next
const req = { user: { role: 'user' } };
const res = {
writeHead: (status) => { responseStatus = status; return res; },
end: () => res
};
const next = () => { accessGranted = true; };
// Usuario con rol insuficiente
verifyRole(['admin'])(req, res, next);
strictEqual(accessGranted, false, "No debería otorgar acceso a usuario sin permisos");
strictEqual(responseStatus, 403, "Debería devolver status 403");
console.log("✓ Test de middleware de autorización superado");
}
testRoleMiddleware();
Este fragmento emplea strictEqual
para verificaciones precisas y notStrictEqual
para asegurar que los valores son diferentes. Existen métodos adicionales como assert.throws()
para probar excepciones o assert.deepStrictEqual()
para examinar la estructura interna de objetos. Cada método está pensado para cubrir diferentes casuísticas en escenarios de test y se puede intercalar con otras técnicas de prueba sin requerir bibliotecas de terceros.
Testing unitario en nodejs
Para estructurar el testing unitario en proyectos de Node.js, es recomendable ubicar los archivos de prueba en una carpeta independiente, a menudo llamada test
. Con esta disposición, se facilita la localización de los test y se crea una separación clara respecto al código fuente. De este modo, cada fichero de prueba puede enfocarse en un único módulo o función, promoviendo la legibilidad y el mantenimiento continuado.
En tests unitarios conviene emplear nombres descriptivos en cada archivo, por ejemplo auth.test.js
o employees.test.js
para identificar rápidamente lo que se está verificando. Además, es útil crear pequeños bloques de verificación centrados en un comportamiento específico, de tal forma que sea sencillo aislar errores y mantener cada comprobación bien definida. Este enfoque minimiza la dependencia entre funcionalidades y favorece la corrección temprana de posibles incidencias.
Con Node.js se puede utilizar el módulo integrado node:test
, que ofrece una manera organizada de agrupar casos de prueba y simplificar la ejecución. Un ejemplo básico podría verse así:
// test/auth.test.js
import test from "node:test";
import { equal, notEqual, rejects } from "node:assert/strict";
import { hashPassword, verifyPassword, validateEmail } from "../auth.js";
test("Validación de email con formato correcto", () => {
const result = validateEmail("usuario@dominio.com");
equal(result, true);
});
test("Validación de email con formato incorrecto", () => {
const result = validateEmail("email-invalido");
equal(result, false);
});
test("Hash de contraseña genera valor diferente", async () => {
const password = "ContraseñaTest123";
const hash = await hashPassword(password);
notEqual(hash, password);
equal(typeof hash, "string");
equal(hash.length > 50, true); // Los hashes bcrypt son largos
});
test("Verificación de contraseña correcta", async () => {
const password = "MiPassword123";
const hash = await hashPassword(password);
const isValid = await verifyPassword(password, hash);
equal(isValid, true);
});
test("Verificación falla con contraseña incorrecta", async () => {
const password = "ContraseñaCorrecta";
const wrongPassword = "ContraseñaIncorrecta";
const hash = await hashPassword(password);
const isValid = await verifyPassword(wrongPassword, hash);
equal(isValid, false);
});
Para las funciones de CRUD que desarrollamos con MySQL:
// test/employees.test.js
import test from "node:test";
import { equal, rejects } from "node:assert/strict";
import { getEmployees, insertEmployee } from "../employees.js";
test("Obtener empleados devuelve un array", async () => {
const employees = await getEmployees();
equal(Array.isArray(employees), true);
});
test("Insertar empleado con datos válidos", async () => {
const newEmployee = {
name: "Juan Pérez",
stand: "Desarrollador",
salary: 50000
};
const result = await insertEmployee(newEmployee);
equal(typeof result.insertId, "number");
equal(result.insertId > 0, true);
});
test("Falla al buscar empleado con ID inválido", async () => {
await rejects(
() => getEmployees({ id: 'texto-invalido' }),
{ message: "ID must be a number" }
);
});
Y para el middleware de autorización:
// test/middleware.test.js
import test from "node:test";
import { equal } from "node:assert/strict";
import { verifyRole } from "../middleware/authorization.js";
test("Middleware permite acceso con rol autorizado", () => {
let nextCalled = false;
const req = { user: { role: 'admin' } };
const res = { writeHead: () => res, end: () => res };
const next = () => { nextCalled = true; };
verifyRole(['admin', 'superuser'])(req, res, next);
equal(nextCalled, true);
});
test("Middleware bloquea acceso con rol insuficiente", () => {
let nextCalled = false;
let statusCode = null;
const req = { user: { role: 'user' } };
const res = {
writeHead: (status) => { statusCode = status; return res; },
end: () => res
};
const next = () => { nextCalled = true; };
verifyRole(['admin'])(req, res, next);
equal(nextCalled, false);
equal(statusCode, 403);
});
Para ejecutar varios archivos de prueba simultáneamente, se puede definir un script en el fichero package.json
. Por ejemplo:
{
"scripts": {
"test": "node --test",
"test:auth": "node --test test/auth.test.js",
"test:employees": "node --test test/employees.test.js",
"test:middleware": "node --test test/middleware.test.js"
}
}
Con esta configuración, al lanzar npm run test
todos los archivos con extensión .test.js se recolectan y se ejecutan de forma unificada. Los scripts específicos permiten ejecutar grupos de pruebas por separado, algo útil durante el desarrollo.
El testing unitario dispone de un alto valor si se ejecuta con regularidad y se revisa ante cada cambio de código. Al testear las funciones reales de autenticación, CRUD y middleware que hemos desarrollado en el curso, podemos detectar regresiones temprano y asegurar que nuestro sistema de usuarios, empleados y autorización funcione correctamente. Una vez establecidos estos hábitos, la calidad y estabilidad del proyecto mejoran de forma significativa sin necesidad de introducir librerías adicionales.
Otras lecciones de Node
Accede a todas las lecciones de Node y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Node.js
Introducción Y Entorno
Fundamentos Del Entorno Node.js
Introducción Y Entorno
Módulo Http Y Https
Http Y Api Rest
Http Params, Headers Y Body
Http Y Api Rest
Validación De Datos
Http Y Api Rest
Conexión A Bases De Datos Sin Orm
Persistencia
Creación De Consultas Básicas (Crud) Sin Orm
Persistencia
Módulo Fs
Sistema De Archivos
Introducción A La Seguridad
Seguridad
Sesiones Y Cookies
Seguridad
Roles Y Permisos
Seguridad
Testing En Node.js
Testing
Estructura De Carpetas
Arquitectura
Configuración Y Variables De Entorno
Arquitectura
En esta lección
Objetivos de aprendizaje de esta lección
- Entender la mecánica básica de las pruebas en Node.js sin librerías.
- Comprobar autenticidad de funciones mediante pruebas escritas a mano.
- Utilizar la aserción nativa de Node.js (assert).
- Implementar el módulo 'node:test' para testing en Node.js.
- Organizar tests unitarios en proyectos Node.js con buenas prácticas.