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
- Yarn
- pnpm
npm install --save-dev react-test-renderer
yarn add --dev react-test-renderer
pnpm add --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
- Yarn
- pnpm
npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
pnpm add --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"
}
}
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:
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>
);
}
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:
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 :
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 :
- 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');
- 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 />);
- Utiliser
react-test-renderer
. Le moteur de rendu de test ne se soucie pas des types d'élément et acceptera volontiers par exempleSomeComponent
. 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. - Désactiver les avertissements dans leur ensemble (devrait être fait dans votre fichier de configuration de jest) :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.
jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));
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
- Yarn
- pnpm
npm install --save-dev @testing-library/react
yarn add --dev @testing-library/react
pnpm add --save-dev @testing-library/react
Implémentons une case à cocher qui permute entre deux libellés :
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>
);
}
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
:
'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.