Probar código asíncrono
Mucho código real es asíncrono: pide datos a un servidor, lee un archivo, espera un temporizador... Devuelve promesas. El reto al probarlo es que el resultado no está disponible de inmediato, así que el test debe esperar.
Hay dos formas de hacerlo bien, y una forma de hacerlo mal.
1. Marcar el test como async y usar await
async function obtenerUsuario(id) {
return { id, nombre: "Ada" }; // en la vida real haría un fetch
}
test("obtenerUsuario devuelve el nombre", async () => {
const usuario = await obtenerUsuario(1);
expect(usuario.nombre).toBe("Ada");
});
2. Devolver la promesa desde el test
Si devuelves la promesa, el runner espera a que se resuelva por ti:
test("obtenerUsuario resuelve", () => {
return obtenerUsuario(1).then((u) => {
expect(u.nombre).toBe("Ada");
});
});
El error clásico: olvidar el
await(o elreturn). El test termina antes de que la promesa se resuelva y la aserción nunca se comprueba: el test "pasa" aunque el código esté roto. Si pruebas async, siempre espera.
Dobles de prueba: mocks y spies
A veces no quieres ejecutar una dependencia real en un test: enviar un email, cobrar una tarjeta o llamar a una API son lentos, costosos o tienen efectos que no queremos en pruebas. La solución son los dobles de prueba: objetos falsos que sustituyen a la dependencia real.
- Un stub devuelve un valor fijo predefinido.
- Un spy (espía) envuelve una función y registra cómo se la llamó (con qué argumentos, cuántas veces) sin cambiar su comportamiento.
- Un mock combina ambas: sustituye la dependencia y permite verificar las interacciones.
La idea central de un spy es guardar cada llamada para inspeccionarla después:
function espiar(fn) {
const espia = (...args) => {
espia.llamadas.push(args); // registra los argumentos
return fn(...args); // delega en la función real
};
espia.llamadas = [];
return espia;
}
const sumarEspiado = espiar((a, b) => a + b);
sumarEspiado(2, 3);
sumarEspiado(10, 1);
console.log(sumarEspiado.llamadas); // [[2, 3], [10, 1]]
console.log(sumarEspiado.llamadas.length); // 2
Con esto, en un test puedes afirmar cosas como "el botón de pago llamó a
cobrar exactamente una vez con el importe correcto", sin cobrar de verdad.
Herramientas como Jest o Vitest traen esto integrado (vi.fn(),
jest.fn()), pero por dentro hacen justo lo que ves arriba.
Ejemplos
Un spy que cuenta llamadas y registra argumentos
function espiar(fn) {
const espia = (...args) => {
espia.llamadas.push(args);
return fn(...args);
};
espia.llamadas = [];
return espia;
}
const saludo = espiar((nombre) => "Hola, " + nombre);
saludo("Ada");
saludo("Linus");
console.log("Devuelve:", saludo("Grace"));
console.log("Veces llamado:", saludo.llamadas.length);
console.log("Llamadas:", JSON.stringify(saludo.llamadas));