Aller au contenu principal
Version: 27.2

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.

Fonctions de rappel (Callbacks)#

Le plus courant des modèles asynchrones est le callback.

Par exemple, disons que vous avez une fonction fetchData(callback) qui récupère des données et appelle callback(data) lorsqu’elle est terminée. 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(data) {
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(data) {
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).

Promesses#

Si votre code utilise des promesses, vous pouvez utiliser une méthode plus simple pour gérer les tests asynchrones. Renvoie une promesse depuis votre test, et Jest attendra que cette promesse soit résolue. Si la promesse est rejetée, le test échouera automatiquement.

Par exemple, disons que fetchData, au lieu d'utiliser un callback, 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');
});
});

Assurez-vous de renvoyer la promesse - 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 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('la récupération échoue avec une erreur', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});

.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');
});

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('la donnée est peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('la récupération échoue avec une erreur', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).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.

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.