Skip to main content
Version: 25.x

Testando Código Assíncrono

É comum em JavaScript executar código de forma assíncrona. Quando você tiver o código que é executado de forma assíncrona, Jest precisa saber quando o código que está testando for concluído, antes que possa passar para outro teste. Jest tem várias maneiras de lidar com isso.

Callbacks#

O padrão assíncrono mais comum são as "callbacks".

Por exemplo, digamos que você tem uma função fetchData(callback) que busca alguns dados e chama callback(data) quando está completa. Você deseja testar que este dado retornado seja apenas a string 'peanut butter'.

Por padrão, testes de Jest completam uma vez que eles chegam ao fim da sua execução. Isso significa que este teste não irá funcionar como o esperado:

// Não faça isso!
test('o dado é manteiga de amendoim', () => {
function callback(data) {
expect(data).toBe('manteiga de amendoim'');
}
fetchData(callback);
});

O problema é que o teste será concluído logo que fetchData completa, antes de sequer chamar a "callback".

Há uma forma alternativa de test que corrige isto. Em vez de colocar o teste em uma função com um argumento vazio, use um único argumento chamado done. Jest aguardará até que a "callback" done é chamada antes de terminar o teste.

test('o dado é manteiga de amendoim', done => {
function callback(data) {
try {
expect(data).toBe('manteiga de amendoim');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});

Se done() nunca for chamado, o teste irá falhar (com erro de timeout), que é o que você quer que aconteça.

Se a instrução expect falhar, ele lança um erro e done() não é chamado. Se queremos ver no log de testes por que falhou, precisamos encapsular expect em um bloco try e passar o erro no bloco catch para o done. Caso contrário, ficamos com um erro de timeout, que não mostra o valor recebido por expect(data).

Promises#

Se seu código usa "promises", ou promessas, há uma maneira mais simples para lidar com testes assíncronos. Retorne uma promise do seu teste, e o Jest vai esperar essa promise ser resolvida. Se a promessa for rejeitada, o teste automaticamente irá falhar.

Por exemplo, digamos que fetchData, ao invés de usar uma "callback", retorna uma promessa que é esperada ser resolvida na string 'peanut butter'. Podemos fazer um teste com:

test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});

Certifique-se de retornar a promise - se você omitir esta instrução return, seu teste será concluído antes que a promessa retornada por fetchData resolva e then() tenha a chance de executar a callback.

Se você espera que a promessa seja rejeitada, use o método .catch. Se certifique de adicionar expect.assertions para verificar que um certo número de afirmações são chamadas. Otherwise, a fulfilled promise would not fail the test.

test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});

.resolves / .rejects#

Você também pode usar o "matcher" .resolves em sua declaração esperada, e Jest irá aguardar a promessa resolver. Se a promessa for rejeitada, o teste automaticamente irá falhar.

test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});

Certifique-se de retornar a afirmação — se você omitir esta instrução de retorno, seu teste será concluído antes que a promessa retornada de fetchData seja resolvida e then() tenha a chance de executar a callback.

Se você espera que uma promessa seja rejeitada, use o "matcher" .rejects. Ele funciona analogicamente para o "matcher" .resolves. Se a promessa é cumprida, o teste automaticamente irá falhar.

test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});

Async/Await#

Como alternativa, você pode usar async e await em seus testes. Para escrever um teste assíncrono, basta usar a palavra-chave async na frente da função passada para test. Por exemplo, o mesmo cenário de fetchData pode ser testado com:

test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});

Você pode combinar async e await com .resolves ou .rejects.

test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});

Nestes casos, async e await são efetivamente apenas uma sintaxe mais simples da mesma lógica dos exemplos de uso de promessas.

Nenhuma dessas formas é particularmente superior às outras, e você pode misturar e combiná-las através de uma base de código, ou até mesmo em um único arquivo. Apenas vai depender de qual estilo torna os testes mais simples. Só depende do estilo que você sente que torna seus testes mais simples.