Testing Asynchronous Code
Se întâmplă frecvent în JavaScript să se execute cod asincron. Atunci când aveţi cod care se execută în mod asincron, Jest trebuie să ştie când codul pe care-l testează s-a completat de executat, înainte de a trece la un alt test. Jest are mai multe moduri pentru a rezolva acest lucru.
Callback-uri
Cel mai frecvent tipar asincron este callback-ul.
De exemplu, să spunem că aveţi o funcţie fetchData(callback)
care preia unele date şi apelează callback(data)
la final. You want to test that this returned data is the 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);
});
Problema este că testul se va termina de îndată ce fetchData
își termină execuția, înainte de a apela vreodată callback-ul.
Există o formă alternativă de test
care rezolvă acest lucru. În loc de a pune testul într-o funcţie cu un argument gol, utilizaţi un singur argument numit done
. Jest va aştepta până când funcția done
este apelată înainte de terminarea testului.
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)
.
Promisiuni
If your code uses promises, there is a more straightforward way to handle asynchronous tests. Return a promise from your test, and Jest will wait for that promise to resolve. În cazul în care promisiunea este respinsă, testul va eşua automat.
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. Asiguraţi-vă că utilizați expect.assertions
pentru a verifica că un anumit număr de aserțiuni sunt executate. 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. În cazul în care promisiunea este respinsă, testul va eşua automat.
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. Funcționează exact ca validatorul .resolves
. În cazul în care promisiunea este îndeplinită, testul va eşua automat.
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
Async/Await
Alternativ, puteţi utiliza async
şi await
în teste. To write an async test, use the async
keyword in front of the function passed to test
. De exemplu, acelaşi scenariu fetchData
poate fi testat cu:
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.