Aller au contenu principal
Version : 29.5

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.

attention

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).

attention

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.