Tester du code asynchrone
Il est fréquent en JavaScript que le code s'exécute de manière asynchrone. Lorsque vous avez du code qui s’exécute de façon asynchrone, Jest doit savoir quand le code qu'il teste est terminé, avant de passer à un autre test. Jest a plusieurs façons de gérer cela.
Promesses
Renvoie une promesse depuis votre test, et Jest attendra que cette promesse soit résolue. Si la promesse est rejetée, le test échouera.
Par exemple, disons que fetchData
renvoie une promesse qui est censée se résoudre par la chaîne 'peanut butter'
. On pourrait le tester avec :
test('la donnée est peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
Async/Await
Vous pouvez également utiliser async
et await
dans vos tests. Pour écrire un test asynchrone, utilisez le mot clé async
devant la fonction passée à test
. Par exemple, le même scénario fetchData
peut être testé avec :
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 (error) {
expect(error).toMatch('error');
}
});
Vous pouvez combiner async
et await
avec .resolves
ou .rejects
.
test('la donnée est peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('la récupération échoue avec une erreur', async () => {
await expect(fetchData()).rejects.toMatch('error');
});
Dans ces cas, async
et await
sont effectivement du sucre syntaxique pour la même logique que celle utilisée dans l'exemple des promesses.
Assurez-vous de retourner (ou attendre
la promesse) - si vous omettez l'instruction return
/await
, votre test se terminera avant que la promesse retournée par fetchData
ne se résout ou ne se rejette.
Si vous vous attendez à ce qu'une promesse soit rejetée, utilisez la méthode .catch
. Assurez-vous de rajouter expect.assertions
pour vérifier qu'un certain nombre d'assertions sont appelées. Sinon, une promesse accomplie ne passerait pas le test.
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(error => expect(error).toMatch('error'));
});
Fonctions de rappel (Callbacks)
Si vous n'utilisez pas de promesses, vous pouvez utiliser des callbacks. Par exemple, supposons que fetchData
, au lieu de retourner une promesse, attend un callback, par exemple il récupère des données et appelle callback(null, data)
quand il est terminé. Vous voulez vérifier que les données retournées correspondent à la chaîne 'peanut butter'
.
Par défaut, les tests Jest se terminent lorsqu'ils parviennent à la fin de leur exécution. Cela signifie que ce test ne fonctionnera pas comme prévu :
// Ne faites pas ça !
test('la donnée est peanut butter', () => {
function callback(error, data) {
if (error) {
throw error;
}
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
Le problème est le suivant : le test se terminera dès que fetchData
sera effectué, avant même d'appeler le callback.
Il y a une autre forme de test
qui résout ce problème. Au lieu de mettre le test dans une fonction avec un argument vide, utilisez un seul argument appelé done
. Jest attendra que le callback done
soit appelé avant de terminer le test.
test('la donnée est peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
Si done()
n'est jamais appelé, le test échouera (avec une erreur de timeout), ce qui correspond à votre souhait.
Si l'instruction expect
échoue, elle lance une erreur et done()
n'est pas appelé. Si nous voulons voir dans le journal de test pourquoi il a échoué, nous devons envelopper expect
dans un bloc try
et passer l'erreur dans le bloc catch
à done
. Autrement, nous nous retrouvons avec une erreur de timeout opaque qui ne montre pas quelle valeur a été reçue par expect(data)
.
Jest will throw an error, if the same test function is passed a done()
callback and returns a promise. This is done as a precaution to avoid memory leaks in your tests.
.resolves
/ .rejects
Vous pouvez également utiliser le comparateur .resolves
dans votre instruction expect, et Jest attendra la résolution de cette promesse. Si la promesse est rejetée, le test échouera automatiquement.
test('la donnée est peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
Assurez-vous de renvoyer l'assertion - si vous omettez cette déclaration return
, votre test se terminera avant que la promesse renvoyée par fetchData
ne se soit résolue et que then() ait une chance d'exécuter le callback.
Si vous vous attendez à ce qu'une promesse soit rejetée, utilisez le comparateur .rejects
. Il fonctionne de manière analogue au comparateur .resolves
. Si la promesse est accomplie, le test échoue automatiquement.
test('la récupération échoue avec une erreur', () => {
return expect(fetchData()).rejects.toMatch('error');
});
Aucune de ces formulations n'est particulièrement supérieure aux autres, et vous pouvez les mélanger dans une base de code ou même dans un seul fichier. Cela dépend simplement du style qui, selon vous, simplifie vos tests.