Mocker les appels API avec React, Jest et fetch-mock

November 27, 2020

Introduction

Récemment dans un projet, nous avons souhaité mettre en place des tests unitaires côté front, afin de sécuriser et optimiser le code de l’application WEB. Notre environnement une application React crée à partir de create-react-app. Il n’est pas concevable d’executer les requetes aux APIs, et donc il est nécessaire de mocker la fonction fetch (ou autre appel aynchrone avec la librairie axios par exemple). Plusieurs solutions s’offrent à nous pour mocker cette fonction fetch, et donc spécifier une valeur de retour. Pour rappel, cette fonction est asynchrone (une promise est retournée)

  • Mocker manuellement window.mock avec Jest, cependant nous verrons qu’il y a quelques inconvénients;
  • Utiliser une librairie externe pour faire cela, nous verrons comment avec la librairie Fetch-Mock;
  • Utiliser un serveur de mock par exemple avec msw, mais je ne voulais pas rentrer dans cette configuration et donc c’est hors scope pour ce post.

Mocker manuellement window.mock

Avec Jest, on peut assez facilement mocker la fonction fetch. Pour ce faire, à chaque fois qu’on lance un test, on a plusieurs options : mocker le retour de la fonction fetch via mockResolvedValue, ou carrément mocker son implémentation via mockImplementation (référence : https://jestjs.io/docs/en/mock-function-api)

Explications :

mockResolvedValue()

On peut mocker le retour de la fonction fetch (qui est une promise pour rappel) grâce à la fonction Jest mockResolvedValue. Veuillez également noter que la fonction json() est également une promise, et donc pensez à bien indiquer async! Cela donne :

window.fetch.mockResolvedValue({
    ok: true,
    json: async () => (/** JSON à retourner */),
  })

mockImplementation()

On a plus de souplesse avec la fonction mockImplementation qu’avec mockResolvedValue. En effet, on va pouvoir définir en fonction de l’URL d’entrée de la fonction fetch, le mock à utiliser. C’est donc plus puissant que la précédente, qui autorise seulement à renvoyer toujours le meme résultat quelquesoit l’URL.

On initialise le mock à chaque test :

beforeEach(() => window.fetch.mockImplementation(mockFetch))`

avec mockFetch défini comme suit par exemple :

async function mockFetch(url, config) {
switch (url) {
    case '/login':
      return {
        ok: true,
        status: 200,
        json: async () => (/* Mock retour pour login */),
      }

    case '/checkout': {
       return {
        ok: true,
        status: 200,
        json: async () => (/* Mock retour pour checkout */),
        }
    }
}

Ainsi, on va pouvoir en fonction de l’URL passsée en paramètre (ici login ou checkout), définir le mock à utiliser de la fonction fetch. Une explication détaillée est présente sur ce superbe article (en anglais) : https://kentcdodds.com/blog/stop-mocking-fetch

fetch-mock

Fetch-Mock est une librairie qui va nous permettre de simplifier le fonctionnement ci-dessus. En effet, on va pouvoir définir le mapping entre URL et valeur de retour de la fonction fetch de manière plus simple qu’avec mockImplementation.

  • Import de la librairie :
import fetchMock from "fetch-mock"`
  • Mock de la fonction fetch :
fetchMock.mock("myUrl/login"), {/*retour attendu par fetch(sans la promise*/}`

Remarque : il est possible de debugger la librairie en ajoutant DEBUG=fetch-mock* en paramètre de Jest, dans le package.json.

Conclusion

Et voilà, nous avons vu différents moyens de mocker cette fonction fetch, et tester en isolation notre frontend. Dans notre projet, nous avons utilisé la troisième approche avec fetch-mock, mais msw peut être intéressant aussi, ce sera l’objet d’un autre post!


Ecrit par Sylvain Maestri qui vit et travaille à Paris, et qui aime construire des applications WEB avec les langages Java & Javascript, et appliquer les bons patterns de programmation SOLID, TDD, BDD et DDD.