Tester des Applications React
At Facebook, we use Jest to test React applications.
Configuration
Configuration avec Create React App
If you are new to React, we recommend using Create React App. It is ready to use and ships with Jest! You will only need to add react-test-renderer for rendering snapshots.
Exécuter
- npm
- Yarn
- pnpm
- Bun
npm install --save-dev react-test-renderer
yarn add --dev react-test-renderer
pnpm add --save-dev react-test-renderer
bun add --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. We are using the babel-jest package and the react babel preset to transform our code inside of the test environment. Also see using babel.
Exécuter
- npm
- Yarn
- pnpm
- Bun
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
bun add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
Your package.json should look something like this (where <current-version> is the actual latest version number for the package). 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'}],
],
};
And you're good to go!
Test Snapshot
Let's create a snapshot test for a Link component that renders hyperlinks:
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. See React: Function and Class Components. Reminders that with Class components, we expect Jest to be used to test props and not methods directly.
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('changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
renderer.act(() => {
tree.props.onMouseEnter();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
renderer.act(() => {
tree.props.onMouseLeave();
});
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
When you run yarn test or jest, this will produce an output file like this:
exports[`changes the class when hovered 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
exports[`changes the class when hovered 2`] = `
<a
className="hovered"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
exports[`changes the class when hovered 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. If the change is expected you can invoke Jest with jest -u to overwrite the existing snapshot.
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.
# Or:
Warning: The tag <SomeComponent> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
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 />); - Use
react-test-renderer. The test renderer doesn't care about element types and will happily accept e.g.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. - 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
- Bun
npm install --save-dev @testing-library/react
yarn add --dev @testing-library/react
pnpm add --save-dev @testing-library/react
bun add --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';
// Note: running cleanup afterEach is done automatically for you in @testing-library/[email protected] or higher
// unmount and cleanup DOM after the test is finished.
afterEach(cleanup);
it('CheckboxWithLabel changes the text after 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. Instead of using babel-jest, here is an example of using @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;
},
};
Don't forget to install the @babel/core and babel-preset-jest packages for this example to work.
To make this work with Jest you need to update your Jest configuration with this: "transform": {"\\.js$": "path/to/custom-transformer.js"}.
If you'd like to build a transformer with babel support, you can also use babel-jest to compose one and pass in your custom configuration options:
const babelJest = require('babel-jest');
module.exports = babelJest.createTransformer({
presets: ['my-custom-preset'],
});
See dedicated docs for more details.