Aller au contenu principal
Version : Suivant

Tester des Applications React

Chez Facebook, nous utilisons Jest pour tester les applications React.

Configuration

Configuration avec Create React App

Si vous débutez avec React, nous vous recommandons d'utiliser Create React App. Il est prêt à être utilisé et est livré avec Jest ! Vous n'aurez besoin d'ajouter que react-test-renderer pour le rendu des snapshots.

Exécuter

npm install --save-dev react-test-renderer

Configuration sans Create React App

Si vous avez déjà une application, vous aurez besoin d'installer quelques paquets pour faire tout fonctionner. Nous utilisons le paquet babel-jest et le préset babel react pour transformer notre code à l’intérieur de l’environnement de test. Voir aussi l'utilisation de babel.

Exécuter

npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer

Votre package.json devrait ressembler à ceci (où <current-version> est la version la plus récente du paquet). Ajoutez les scripts et la configuration Jest au fichier:

{
"dependencies": {
"react": "<current-version>",
"react-dom": "<current-version>"
},
"devDependencies": {
"@babel/preset-env": "<current-version>",
"@babel/preset-react": "<current-version>",
"babel-jest": "<current-version>",
"jest": "<current-version>",
"react-test-renderer": "<current-version>"
},
"scripts": {
"test": "jest"
}
}
babel.config.js
module.exports = {
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};

Et vous voilà prêt à partir !

Test Snapshot

Nous allons créer un snapshot pour un composant lien qui afficher des liens hypertexte:

Link.js
import {useState} from 'react';

const STATUS = {
HOVERED: 'hovered',
NORMAL: 'normal',
};

export default function Link({page, children}) {
const [status, setStatus] = useState(STATUS.NORMAL);

const onMouseEnter = () => {
setStatus(STATUS.HOVERED);
};

const onMouseLeave = () => {
setStatus(STATUS.NORMAL);
};

return (
<a
className={status}
href={page || '#'}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{children}
</a>
);
}
remarque

Examples are using Function components, but Class components can be tested in the same way. Consultez React : Fonctions composants et composants à base de classes. Rappellez-vous qu'avec les composants à base de classe, nous attendons que Jest soit utilisé pour tester des props et non pas des méthodes directement.

Utilisons maintenant le moteur de rendu de React and la fonctionnalité de snapshot de Jest pour capturer le rendu du composant dans un fichier:

Link.test.js
import renderer from 'react-test-renderer';
import Link from '../Link';

it('change la classe lorsqu\'on le survole', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();

// déclenche manuellement le callback
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendu
tree = component.toJSON();
expect(tree).toMatchSnapshot();

// déclenche manuellement le callback
renderer.act(() => {
tree.props.onMouseLeave();
});
// re-rendu
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});

Lorsque vous exécutez yarn test ou jest, cela produira un fichier de sortie comme ceci :

__tests__/__snapshots__/Link.test.js.snap
exports[`change la classe lorsqu'on le survole 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

exports[`change la classe lorsqu'on le survole 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

exports[`change la classe lorsqu'on le survole 3`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

La prochaine fois que vous exécuterez les tests, la sortie rendue sera comparée au snapshot précédemment créé. Le snapshot doit être committé avec les changements de code. Lorsqu'un test de snapshot échoue, vous devez vérifier s'il s'agit d'un changement intentionnel ou non intentionnel. Si le changement est attendu, vous pouvez appeler Jest avec jest -u pour écraser le snapshot existant.

The code for this example is available at examples/snapshot.

Tests de snapshots avec des simulations, Enzyme et React 16+

Il y a une mise en garde autour des tests de snapshot lorsque vous utilisez Enzyme et React 16+. Si vous simulez un module en utilisant le style suivant :

jest.mock('../SomeDirectory/SomeComponent', () => 'SomeComponent');

Ensuite, vous verrez des avertissements dans la console :

Warning: <SomeComponent /> is using uppercase HTML. Always use lowercase HTML tags in React.

# Ou :
Warning: The tag <SomeComponent> is unrecognized in this browser. Si vous vouliez rendre un composant React, commencez son nom avec une lettre majuscule.

React 16 déclenche ces avertissements en raison de la façon dont il vérifie les types d'éléments, et le module simulé échoue à ces vérifications. Vos options sont :

  1. Rendre en tant que texte. De cette façon, vous ne verrez pas les propriétés passées au composant simulé dans l'instantané, mais c'est simple :
    jest.mock('./SomeComponent', () => () => 'SomeComponent');
  2. Rendre en tant qu'élément personnalisé. Les « éléments personnalisés » du DOM ne sont pas vérifiés et ne devraient pas déclencher d'avertissements. Ils sont en minuscule et ont un tiret dans le nom.
    jest.mock('./Widget', () => () => <mock-widget />);
  3. Utiliser react-test-renderer. Le moteur de rendu de test ne se soucie pas des types d'élément et acceptera volontiers par exemple SomeComponent. Vous pouvez vérifier les instantanés en utilisant le moteur de rendu de test et vérifier le comportement des composants séparément en utilisant Enzyme.
  4. Désactiver les avertissements dans leur ensemble (devrait être fait dans votre fichier de configuration de jest) :
    jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
    Cela ne devrait normalement pas être votre choix car des avertissements utiles pourraient être perdus. Cependant, dans certains cas, par exemple lorsque nous testons les composants de react-natif, nous rendons les balises react-native dans le DOM et de nombreux avertissements ne sont pas pertinents. Une autre option est de swizzler la console.warn et supprimer les avertissements spécifiques.

Test du DOM

If you'd like to assert, and manipulate your rendered components you can use @testing-library/react, Enzyme, or React's TestUtils. The following example use @testing-library/react.

@testing-library/react

npm install --save-dev @testing-library/react

Implémentons une case à cocher qui permute entre deux libellés :

CheckboxWithLabel.js
import {useState} from 'react';

export default function CheckboxWithLabel({labelOn, labelOff}) {
const [isChecked, setIsChecked] = useState(false);

const onChange = () => {
setIsChecked(!isChecked);
};

return (
<label>
<input type="checkbox" checked={isChecked} onChange={onChange} />
{isChecked ? labelOn : labelOff}
</label>
);
}
__tests__/CheckboxWithLabel-test.js
import {cleanup, fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from '../CheckboxWithLabel';

// Remarque : l'exécution du nettoyage afterEach se fait automatiquement pour vous dans @testing-library/react@9.0.0 ou une version supérieure.
// Démonte et nettoie le DOM après que le test soit terminé.
afterEach(cleanup);

it('CheckboxWithLabel change le texte après click', () => {
const {queryByLabelText, getByLabelText} = render(
<CheckboxWithLabel labelOn="On" labelOff="Off" />,
);

expect(queryByLabelText(/off/i)).toBeTruthy();

fireEvent.click(getByLabelText(/off/i));

expect(queryByLabelText(/on/i)).toBeTruthy();
});

The code for this example is available at examples/react-testing-library.

Transformateurs personnalisés

Si vous avez besoin de fonctionnalités plus avancées, vous pouvez également construire votre propre transformateur. Au lieu d'utiliser babel-jest, voici un exemple d'utilisation de @babel/core :

custom-transformer.js
'use strict';

const {transform} = require('@babel/core');
const jestPreset = require('babel-preset-jest');

module.exports = {
process(src, filename) {
const result = transform(src, {
filename,
presets: [jestPreset],
});

return result || src;
},
};

N'oubliez pas d'installer les paquets @babel/core et babel-preset-jest pour que cet exemple fonctionne.

Pour que cela fonctionne avec Jest, vous devez mettre à jour votre configuration Jest avec ceci : "transform": {"\\.js$": "path/to/custom-transformer.js"}.

Si vous souhaitez construire un transformateur avec le support de babel, vous pouvez également utiliser babel-jest pour en composer un et y passer vos options de configuration personnalisées :

const babelJest = require('babel-jest');

module.exports = babelJest.createTransformer({
presets: ['my-custom-preset'],
});

Consultez la documentation dédiée pour plus de détails.