Testing Asynchronous Code
É 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'
.
By default, Jest tests complete once they reach the end of their execution. That means this test will not work as intended:
// Don't do this!
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter');
}
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('the data is peanut butter', done => {
function callback(data) {
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
If done()
is never called, the test will fail (with timeout error), which is what you want to happen.
If the expect
statement fails, it throws an error and done()
is not called. If we want to see in the test log why it failed, we have to wrap expect
in a try
block and pass the error in the catch
block to done
. Otherwise, we end up with an opaque timeout error that doesn't show what value was received by 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.
For example, let's say that fetchData
, instead of using a callback, returns a promise that is supposed to resolve to the string 'peanut butter'
. We could test it with:
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
Be sure to return the promise - if you omit this return
statement, your test will complete before the promise returned from fetchData
resolves and then() has a chance to execute the callback.
If you expect a promise to be rejected, use the .catch
method. 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
You can also use the .resolves
matcher in your expect statement, and Jest will wait for that promise to resolve. Se a promessa for rejeitada, o teste automaticamente irá falhar.
test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
Be sure to return the assertion—if you omit this return
statement, your test will complete before the promise returned from fetchData
is resolved and then() has a chance to execute the callback.
If you expect a promise to be rejected, use the .rejects
matcher. 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');
}
});
You can combine async
and await
with .resolves
or .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.toThrow('error');
});
In these cases, async
and await
are effectively syntactic sugar for the same logic as the promises example uses.
None of these forms is particularly superior to the others, and you can mix and match them across a codebase or even in a single file. It just depends on which style you feel makes your tests simpler.