Tests unitaires dans les applications natives React

Publié: 2022-03-10
Résumé rapide ↬ Les tests unitaires font désormais partie intégrante du processus de développement logiciel. C'est le niveau de test auquel les composants du logiciel sont testés. Dans ce didacticiel, vous apprendrez à tester les unités d'une application React Native.

React Native est l'un des frameworks les plus utilisés pour créer des applications mobiles. Ce didacticiel est destiné aux développeurs qui souhaitent commencer à tester les applications React Native qu'ils créent. Nous utiliserons le framework de test Jest et Enzyme.

Dans cet article, nous allons apprendre les principes de base du test, explorer diverses bibliothèques pour tester une application et voir comment tester les unités (ou composants) d'une application React Native. En travaillant avec une application React Native, nous consoliderons nos connaissances en matière de tests.

Remarque : Une connaissance de base de JavaScript et de React Native serait très utile pour suivre ce didacticiel.

Qu'est-ce que les tests unitaires ?

Le test unitaire est le niveau de test auquel les composants individuels du logiciel sont testés. Nous le faisons pour nous assurer que chaque composant fonctionne comme prévu. Un composant est la plus petite partie testable du logiciel.

Pour illustrer, créons un composant Button et simulons un test unitaire :

 import React from 'react'; import { StyleSheet, Text, TouchableOpacity } from 'react-native'; function AppButton({ onPress }) { return ( <TouchableOpacity style={[styles.button, { backgroundColor: colors[color] }]} onPress={onPress} > <Text style={styles.text}>Register</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ button: { backgroundColor: red; borderRadius: 25, justifyContent: 'center', alignItems: 'center', }, text: { color: #fff } }) export default AppButton;

Ce composant Button contient du texte et une fonction onPress . Testons ce composant pour voir en quoi consistent les tests unitaires.

Commençons par créer un fichier de test, nommé Button.test.js :

 it('renders correctly across screens', () => { const tree = renderer.create(<Button />).toJSON(); expect(tree).toMatchSnapshot(); });

Ici, nous testons pour voir si notre composant Button s'affiche comme il se doit sur tous les écrans de l'application. C'est à cela que servent les tests unitaires : tester les composants d'une application pour s'assurer qu'ils fonctionnent comme ils le devraient.

Tests unitaires dans les applications natives React

Une application React Native peut être testée avec une variété d'outils, dont certains sont les suivants :

  • WebDriver
    Cet outil de test open source pour les applications Node.js est également utilisé pour tester les applications React Native.
  • Cauchemar
    Cela automatise les opérations de test dans le navigateur. Selon la documentation, "l'objectif est d'exposer quelques méthodes simples qui imitent les actions de l'utilisateur (comme goto , type et click ), avec une API qui semble synchrone pour chaque bloc de script, plutôt que des rappels profondément imbriqués".
  • Plaisanter
    C'est l'une des bibliothèques de test les plus populaires et celle sur laquelle nous allons nous concentrer aujourd'hui. Comme React, il est maintenu par Facebook et a été conçu pour fournir une configuration "zéro configuration" pour des performances maximales.
  • Moka
    Mocha est une bibliothèque populaire pour tester les applications React et React Native. Il est devenu un outil de test de choix pour les développeurs en raison de sa facilité de configuration et d'utilisation et de sa rapidité.
  • Jasmin
    Selon sa documentation, Jasmine est un framework de développement basé sur le comportement pour tester le code JavaScript.
Plus après saut! Continuez à lire ci-dessous ↓

Introduction à Jest et Enzyme

Selon sa documentation, "Jest est un charmant framework de test JavaScript axé sur la simplicité". Il fonctionne avec une configuration zéro. Lors de l'installation (à l'aide d'un gestionnaire de packages tel que npm ou Yarn), Jest est prêt à l'emploi, sans aucune autre installation nécessaire.

Enzyme est un framework de test JavaScript pour les applications React Native. (Si vous travaillez avec React plutôt que React Native, un guide est disponible.) Nous utiliserons Enzyme pour tester les unités de la sortie de notre application. Avec lui, nous pouvons simuler l'exécution de l'application.

Commençons par configurer notre projet. Nous utiliserons l'application Done With It sur GitHub. C'est un marché d'applications React Native. Commencez par le cloner, naviguez dans le dossier et installez les packages en exécutant ce qui suit pour npm…

 npm install

… ou ceci pour Yarn :

 yarn install

Cette commande installera tous les packages de notre application. Une fois cela fait, nous testerons la cohérence de l'interface utilisateur de notre application à l'aide d'instantanés, décrits ci-dessous.

Instantanés et configuration de plaisanterie

Dans cette section, nous testerons les touches de l'utilisateur et l'interface utilisateur des composants de l'application en testant des instantanés à l'aide de Jest.

Avant de faire cela, nous devons installer Jest et ses dépendances. Pour installer Jest for Expo React Native, exécutez la commande suivante :

 yarn add jest-expo --dev

Cela installe jest-expo dans le répertoire de notre application. Ensuite, nous devons mettre à jour notre fichier package.json pour avoir un script de test :

 "scripts": { "test" "jest" }, "jest": { "preset": "jest-expo" }

En ajoutant cette commande, nous indiquons à Jest quel package enregistrer dans notre application et où.

Ensuite, ajoutez d'autres packages à notre application qui aideront Jest à effectuer un test complet. Pour npm, lancez ceci…

 npm i react-test-renderer --save-dev

… et pour Yarn, ceci :

 yarn add react-test-renderer --dev

Il nous reste encore un peu de configuration à faire dans notre fichier package.json . Selon la documentation d'Expo React Native, nous devons ajouter une configuration transformIgnorePattern qui empêche les tests de s'exécuter dans Jest chaque fois qu'un fichier source correspond à un test (c'est-à-dire si un test est effectué et qu'un fichier similaire est trouvé dans les node modules du projet).

 "jest": { "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)" ] }

Maintenant, créons un nouveau fichier, nommé App.test.js , pour écrire notre premier test. Nous allons tester si notre App a un élément enfant dans son arborescence :

 import React from "react"; import renderer from "react-test-renderer"; import App from "./App.js" describe("<App />", () => { it('has 1 child', () => { const tree = renderer.create(<App />).toJSON(); expect(tree.children.length).toBe(1); }); });

Maintenant, exécutez yarn test ou son équivalent npm. Si App.js a un seul élément enfant, notre test devrait réussir, ce qui sera confirmé dans l'interface de ligne de commande.

Dans le code ci-dessus, nous avons importé React et react-test-renderer , qui rend nos tests pour Expo . Nous avons converti l'arborescence des composants <App /> en JSON, puis demandé à Jest de voir si le nombre de composants enfants renvoyés dans JSON correspond à ce que nous attendions.

Plus de tests instantanés

Comme le dit David Adeneye :

"Un test d'instantané permet de s'assurer que l'interface utilisateur (UI) d'une application Web ne change pas de manière inattendue. Il capture le code d'un composant à un moment donné, afin que nous puissions comparer le composant dans un état avec n'importe quel autre état possible qu'il pourrait prendre.

Cela se fait en particulier lorsqu'un projet implique des styles globaux qui sont utilisés dans un grand nombre de composants. Écrivons un test d'instantané pour App.js afin de tester la cohérence de son interface utilisateur :

 it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); }); it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });

Ajoutez ceci aux tests que nous avons déjà écrits, puis exécutez yarn test (ou son équivalent npm). Si notre test réussit, nous devrions voir ceci :

 PASS src/App.test.js √ has 1 child (16ms) √ renders correctly (16ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 1 total Time: 24s

Cela nous indique que nos tests ont réussi et le temps qu'ils ont pris. Votre résultat sera similaire si les tests ont réussi.

Passons à la moquerie de certaines fonctions dans Jest.

Appels d'API moqueurs

Selon la documentation de Jest :

Les fonctions fictives vous permettent de tester les liens entre le code en effaçant l'implémentation réelle d'une fonction, en capturant les appels à la fonction (et les paramètres passés dans ces appels), en capturant les instances des fonctions constructeur lorsqu'elles sont instanciées avec `new` et en permettant test- configuration temporelle des valeurs de retour.

En termes simples, une simulation est une copie d'un objet ou d'une fonction sans le fonctionnement réel de cette fonction. Il imite cette fonction.

Les simulations nous aident à tester les applications de nombreuses façons, mais le principal avantage est qu'elles réduisent notre besoin de dépendances.

Les simulations peuvent généralement être effectuées de deux manières. L'une consiste à créer une fonction fictive qui est injectée dans le code à tester. L'autre consiste à écrire une fonction fictive qui remplace le package ou la dépendance attachée au composant.

La plupart des organisations et des développeurs préfèrent écrire des simulations manuelles qui imitent les fonctionnalités et utilisent de fausses données pour tester certains composants.

React Native inclut la fetch dans l'objet global. Pour éviter de faire de vrais appels d'API dans notre test unitaire, nous nous moquons d'eux. Vous trouverez ci-dessous un moyen de se moquer de tous, sinon de la plupart, de nos appels d'API dans React Native, et sans avoir besoin de dépendances :

 global.fetch = jest.fn(); // mocking an API success response once fetch.mockResponseIsSuccess = (body) => { fetch.mockImplementationForOnce ( () => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))}) ); }; // mocking an API failure response for once fetch.mockResponseIsFailure = (error) => { fetch.mockImplementationForOnce( () => Promise.reject(error) ); };

Ici, nous avons écrit une fonction qui essaie de récupérer une API une fois. Ceci fait, il renvoie une promesse, et lorsqu'il est résolu, il renvoie le corps en JSON. C'est similaire à la réponse fictive pour une transaction de récupération qui a échoué — elle renvoie une erreur.

Vous trouverez ci-dessous le composant product de notre application, contenant un objet product et renvoyant les informations sous forme d' props .

 import React from 'react'; const Product = () => { const product = { name: 'Pizza', quantity: 5, price: '$50' } return ( <> <h1>Name: {product.name}</h1> <h1>Quantity: {product.quantity}</h1> <h1>Price: {product.price}</h1> </> ); } export default Product;

Imaginons que nous essayons de tester tous les composants de notre produit. Accéder directement à notre base de données n'est pas une solution envisageable. C'est là que les simulations entrent en jeu. Dans le code ci-dessous, nous essayons de simuler un composant du produit en utilisant Jest pour décrire les objets du composant.

 describe("", () => { it("accepts products props", () => { const wrapper = mount(<Customer product={product} />); expect(wrapper.props().product).toEqual(product); }); it("contains products quantity", () => { expect(value).toBe(3); }); });

Nous utilisons describe de Jest pour dicter les tests que nous voulons faire. Dans le premier test, nous vérifions si l'objet que nous passons est égal aux accessoires dont nous nous sommes moqués.

Dans le deuxième test, nous passons les accessoires du customer pour nous assurer qu'il s'agit d'un produit et qu'il correspond à nos maquettes. Ce faisant, nous n'avons pas à tester tous les composants de notre produit, et nous pouvons également éviter les bogues dans notre code.

Se moquer des requêtes d'API externes

Jusqu'à présent, nous avons effectué des tests pour les appels d'API avec d'autres éléments de notre application. Maintenant, simulons un appel d'API externe. Nous allons utiliser Axios. Pour tester un appel externe à une API, nous devons simuler nos requêtes et également gérer les réponses que nous obtenons. Nous allons utiliser axios-mock-adapter pour simuler Axios. Tout d'abord, nous devons installer axios-mock-adapter en exécutant la commande ci-dessous :

 yarn add axios-mock-adapter

La prochaine chose à faire est de créer nos maquettes :

 import MockAdapter from 'axios-mock-adapter'; import Faker from 'faker' import ApiClient from '../constants/api-client'; import userDetails from 'jest/mockResponseObjects/user-objects'; let mockApi = new MockAdapter(ApiClient.getAxiosInstance()); let validAuthentication = { name: Faker.internet.email(), password: Faker.internet.password() mockApi.onPost('requests').reply(config) => { if (config.data === validAuthentication) { return [200, userDetails]; } return [400, 'Incorrect username and password']; });

Ici, nous appelons ApiClient et lui transmettons une instance Axios pour simuler les informations d'identification de l'utilisateur. Nous utilisons un package nommé faker.js pour générer de fausses données utilisateur, telles qu'une adresse e-mail et un mot de passe.

La maquette se comporte comme nous nous attendons à ce que l'API le fasse. Si la demande aboutit, nous recevrons une réponse avec un code d'état de 200 pour OK. Et nous obtiendrons un code d'état de 400 pour une mauvaise demande au serveur, qui serait envoyé avec JSON avec le message « Nom d'utilisateur et mot de passe incorrects ».

Maintenant que notre maquette est prête, écrivons un test pour une requête API externe. Comme précédemment, nous utiliserons des instantanés.

 it('successful sign in with correct credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(getActions()).toMatchSnapshot(); }); it('unsuccessful sign in with wrong credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'wrong credential')) .catch((error) => { expect(errorObject).toMatchSnapshot(); });

Ici, nous testons une connexion réussie avec les informations d'identification correctes, en utilisant l' async await JavaScript native pour conserver nos entrées. Pendant ce temps, la fonction authenticateUser de Jest authentifie la demande et s'assure qu'elle correspond à nos instantanés précédents. Ensuite, nous testons une connexion infructueuse en cas d'informations d'identification erronées, telles qu'une adresse e-mail ou un mot de passe, et nous envoyons une erreur en réponse.

Maintenant, lancez yarn test ou npm test . Je suis sûr que tous vos tests passeront.

Voyons comment tester des composants dans une bibliothèque de gestion d'état, Redux.

Test des actions Redux et des réducteurs à l'aide d'instantanés

Il est indéniable que Redux est l'un des gestionnaires d'état les plus utilisés pour les applications React. La plupart des fonctionnalités de Redux impliquent un dispatch , qui est une fonction du magasin Redux utilisée pour déclencher un changement d'état d'une application. Tester Redux peut être délicat car les actions de Redux augmentent rapidement en taille et en complexité. Avec les instantanés Jest, cela devient plus facile. La plupart des tests avec Redux se résument à deux choses :

  • Pour tester les actions , nous créons redux-mock-store et distribuons les actions.
  • Pour tester les réducteurs, nous importons le reducer et lui transmettons un état et un objet d'action.

Vous trouverez ci-dessous un test Redux avec des instantanés. Nous allons tester les actions envoyées en authentifiant l'utilisateur lors de la SIGN-IN et voir comment l'action LOGOUT est gérée par le réducteur user .

 import mockStore from 'redux-mock-store'; import { LOGOUT } from '../actions/logout'; import User from '../reducers/user'; import { testUser } from 'jest/mock-objects'; describe('Testing the sign in authentication', () => { const store = mockStore(); it('user attempts with correct password and succeeds', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(store.getActions()).toMatchSnapshot(); }); }); describe('Testing reducers after user LOGS OUT', () => { it('user is returned back to initial app state', () => { expect(user(testUser, { type: LOGOUT })).toMatchSnapshot(); }); });

Dans le premier test, nous décrivons l'authentification de connexion et créons un magasin fictif. Pour ce faire, nous importons d'abord un mockStore depuis Redux, puis importons une méthode nommée testUser depuis Jest pour nous aider à nous moquer d'un utilisateur. Ensuite, nous testons le moment où l'utilisateur se connecte avec succès à l'application à l'aide d'une adresse e-mail et d'un mot de passe qui correspondent à ceux de notre magasin d'instantanés. Ainsi, l'instantané garantit que les objets saisis par l'utilisateur correspondent à chaque fois qu'un test est exécuté.

Dans le deuxième test, nous testons le moment où l'utilisateur se déconnecte. Une fois que notre instantané de réducteur confirme qu'un utilisateur s'est déconnecté, il revient à l'état initial de l'application.

Ensuite, nous testons en exécutant yarn test . Si les tests ont réussi, nous devrions voir le résultat suivant :

 PASS src/redux/actions.test.js √ user attempts with correct password and succeeds (23ms) √ user is returned back to initial app state (19ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 2 total Time: 31s

Conclusion

Avec Jest, tester les applications React Native n'a jamais été aussi facile, en particulier avec les instantanés, qui garantissent que l'interface utilisateur reste cohérente quels que soient les styles globaux. De plus, Jest nous permet de simuler certains appels et modules d'API dans notre application. Nous pouvons aller plus loin en testant les composants d'une application React Native.

Autres ressources

  • "Un guide pratique pour tester les applications natives React avec Jest", David Adeneye, Smashing Magazine
  • Documentation de plaisanterie
  • "Tester avec Jest", documentation Expo React Native
  • "Apprendre à tester React Native With Jest", Jason Gaare