Testar código assíncrono
Muito código real é assíncrono: pede dados a um servidor, lê um arquivo, espera um temporizador... Retorna promises. O desafio ao testá-lo é que o resultado não está disponível de imediato, então o teste deve esperar.
Há duas formas de fazer isso certo, e uma forma de fazer errado.
1. Marcar o teste como async e usar await
async function obterUsuario(id) {
return { id, nome: "Ada" }; // na vida real faria um fetch
}
test("obterUsuario retorna o nome", async () => {
const usuario = await obterUsuario(1);
expect(usuario.nome).toBe("Ada");
});
2. Retornar a promise a partir do teste
Se você retornar a promise, o runner espera ela ser resolvida por você:
test("obterUsuario resolve", () => {
return obterUsuario(1).then((u) => {
expect(u.nome).toBe("Ada");
});
});
O erro clássico: esquecer o
await(ou oreturn). O teste termina antes de a promise ser resolvida e a asserção nunca é verificada: o teste "passa" mesmo que o código esteja quebrado. Se você testar async, sempre espere.
Dublês de teste: mocks e spies
Às vezes você não quer executar uma dependência real em um teste: enviar um e-mail, cobrar um cartão ou chamar uma API são lentos, custosos ou têm efeitos que não queremos nos testes. A solução são os dublês de teste: objetos falsos que substituem a dependência real.
- Um stub retorna um valor fixo predefinido.
- Um spy (espião) envolve uma função e registra como ela foi chamada (com quais argumentos, quantas vezes) sem mudar seu comportamento.
- Um mock combina os dois: substitui a dependência e permite verificar as interações.
A ideia central de um spy é guardar cada chamada para inspecioná-la depois:
function espiar(fn) {
const espiao = (...args) => {
espiao.chamadas.push(args); // registra os argumentos
return fn(...args); // delega para a função real
};
espiao.chamadas = [];
return espiao;
}
const somarEspiado = espiar((a, b) => a + b);
somarEspiado(2, 3);
somarEspiado(10, 1);
console.log(somarEspiado.chamadas); // [[2, 3], [10, 1]]
console.log(somarEspiado.chamadas.length); // 2
Com isso, em um teste você pode afirmar coisas como "o botão de pagamento chamou
cobrar exatamente uma vez com o valor correto", sem cobrar de verdade.
Ferramentas como Jest ou Vitest trazem isso integrado (vi.fn(),
jest.fn()), mas por dentro elas fazem exatamente o que você vê acima.
Exemplos
Um spy que conta chamadas e registra argumentos
function espiar(fn) {
const espiao = (...args) => {
espiao.chamadas.push(args);
return fn(...args);
};
espiao.chamadas = [];
return espiao;
}
const saudacao = espiar((nome) => "Olá, " + nome);
saudacao("Ada");
saudacao("Linus");
console.log("Retorna:", saudacao("Grace"));
console.log("Vezes chamado:", saudacao.chamadas.length);
console.log("Chamadas:", JSON.stringify(saudacao.chamadas));