Testing React Apps

    If you are new to React, we recommend using Create React App. It is ready to use and ! You will only need to add for rendering snapshots.

    Run

    • npm
    • Yarn
    • pnpm
    1. yarn add --dev react-test-renderer
    1. pnpm add --save-dev react-test-renderer

    If you have an existing application you’ll need to install a few packages to make everything work well together. We are using the babel-jest package and the react babel preset to transform our code inside of the test environment. Also see .

    Run

    • npm
    • Yarn
    • pnpm
    1. npm install --save-dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
    1. yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
    1. pnpm add --save-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). Please add the scripts and jest configuration entries:

    1. {
    2. "dependencies": {
    3. "react": "<current-version>",
    4. "react-dom": "<current-version>"
    5. },
    6. "devDependencies": {
    7. "@babel/preset-env": "<current-version>",
    8. "@babel/preset-react": "<current-version>",
    9. "babel-jest": "<current-version>",
    10. "jest": "<current-version>",
    11. "react-test-renderer": "<current-version>"
    12. },
    13. "scripts": {
    14. "test": "jest"
    15. }
    16. }

    babel.config.js

    1. module.exports = {
    2. presets: [
    3. '@babel/preset-env',
    4. ['@babel/preset-react', {runtime: 'automatic'}],
    5. ],
    6. };

    And you’re good to go!

    Let’s create a for a Link component that renders hyperlinks:

    Link.js

    1. import {useState} from 'react';
    2. const STATUS = {
    3. HOVERED: 'hovered',
    4. NORMAL: 'normal',
    5. };
    6. export default function Link({page, children}) {
    7. const [status, setStatus] = useState(STATUS.NORMAL);
    8. const onMouseEnter = () => {
    9. setStatus(STATUS.HOVERED);
    10. };
    11. const onMouseLeave = () => {
    12. setStatus(STATUS.NORMAL);
    13. };
    14. return (
    15. <a
    16. className={status}
    17. href={page || '#'}
    18. onMouseLeave={onMouseLeave}
    19. >
    20. {children}
    21. </a>
    22. );
    23. }

    note

    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.

    Now let’s use React’s test renderer and Jest’s snapshot feature to interact with the component and capture the rendered output and create a snapshot file:

    When you run yarn test or , this will produce an output file like this:

    __tests__/__snapshots__/Link.test.js.snap

    1. exports[`changes the class when hovered 1`] = `
    2. <a
    3. className="normal"
    4. href="http://www.facebook.com"
    5. onMouseEnter={[Function]}
    6. onMouseLeave={[Function]}
    7. >
    8. Facebook
    9. </a>
    10. `;
    11. exports[`changes the class when hovered 2`] = `
    12. <a
    13. className="hovered"
    14. href="http://www.facebook.com"
    15. onMouseEnter={[Function]}
    16. onMouseLeave={[Function]}
    17. >
    18. Facebook
    19. </a>
    20. `;
    21. exports[`changes the class when hovered 3`] = `
    22. <a
    23. className="normal"
    24. href="http://www.facebook.com"
    25. onMouseEnter={[Function]}
    26. onMouseLeave={[Function]}
    27. >
    28. Facebook
    29. </a>
    30. `;

    The next time you run the tests, the rendered output will be compared to the previously created snapshot. The snapshot should be committed along with code changes. When a snapshot test fails, you need to inspect whether it is an intended or unintended change. 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 .

    Snapshot Testing with Mocks, Enzyme and React 16+

    There’s a caveat around snapshot testing when using Enzyme and React 16+. If you mock out a module using the following style:

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

    Then you will see warnings in the console:

    1. Warning: <SomeComponent /> is using uppercase HTML. Always use lowercase HTML tags in React.
    2. # Or:
    3. 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 triggers these warnings due to how it checks element types, and the mocked module fails these checks. Your options are:

    1. Render as text. This way you won’t see the props passed to the mock component in the snapshot, but it’s straightforward:

      1. jest.mock('./SomeComponent', () => () => 'SomeComponent');
    2. Render as a custom element. DOM “custom elements” aren’t checked for anything and shouldn’t fire warnings. They are lowercase and have a dash in the name.

      1. jest.mock('./Widget', () => () => <mock-widget />);
    3. Use react-test-renderer. The test renderer doesn’t care about element types and will happily accept e.g. SomeComponent. You could check snapshots using the test renderer, and check component behavior separately using Enzyme.

    4. Disable warnings all together (should be done in your jest setup file):

      1. jest.mock('fbjs/lib/warning', () => require('fbjs/lib/emptyFunction'));

      This shouldn’t normally be your option of choice as useful warnings could be lost. However, in some cases, for example when testing react-native’s components we are rendering react-native tags into the DOM and many warnings are irrelevant. Another option is to swizzle the console.warn and suppress specific warnings.

    react-testing-library

    • npm
    • Yarn
    • pnpm
    1. npm install --save-dev @testing-library/react
    1. yarn add --dev @testing-library/react

    Let’s implement a checkbox which swaps between two labels:

    CheckboxWithLabel.js

    1. import {useState} from 'react';
    2. export default function CheckboxWithLabel({labelOn, labelOff}) {
    3. const [isChecked, setIsChecked] = useState(false);
    4. const onChange = () => {
    5. setIsChecked(!isChecked);
    6. };
    7. <label>
    8. <input type="checkbox" checked={isChecked} onChange={onChange} />
    9. {isChecked ? labelOn : labelOff}
    10. </label>
    11. );
    12. }

    __tests__/CheckboxWithLabel-test.js

    1. import CheckboxWithLabel from '../CheckboxWithLabel';
    2. // Note: running cleanup afterEach is done automatically for you in @testing-library/react@9.0.0 or higher
    3. // unmount and cleanup DOM after the test is finished.
    4. afterEach(cleanup);
    5. it('CheckboxWithLabel changes the text after click', () => {
    6. const {queryByLabelText, getByLabelText} = render(
    7. <CheckboxWithLabel labelOn="On" labelOff="Off" />,
    8. );
    9. expect(queryByLabelText(/off/i)).toBeTruthy();
    10. fireEvent.click(getByLabelText(/off/i));
    11. expect(queryByLabelText(/on/i)).toBeTruthy();
    12. });

    The code for this example is available at .

    Enzyme

    • npm
    • Yarn
    • pnpm
    1. npm install --save-dev enzyme
    1. yarn add --dev enzyme
    1. pnpm add --save-dev enzyme

    If you are using a React version below 15.5.0, you will also need to install react-addons-test-utils.

    Let’s rewrite the test from above using Enzyme instead of react-testing-library. We use Enzyme’s in this example.

    __tests__/CheckboxWithLabel-test.js

    1. import Enzyme, {shallow} from 'enzyme';
    2. import Adapter from 'enzyme-adapter-react-16';
    3. import CheckboxWithLabel from '../CheckboxWithLabel';
    4. Enzyme.configure({adapter: new Adapter()});
    5. it('CheckboxWithLabel changes the text after click', () => {
    6. // Render a checkbox with label in the document
    7. const checkbox = shallow(<CheckboxWithLabel labelOn="On" labelOff="Off" />);
    8. expect(checkbox.text()).toBe('Off');
    9. checkbox.find('input').simulate('change');
    10. expect(checkbox.text()).toBe('On');
    11. });

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

    If you need more advanced functionality, you can also build your own transformer. Instead of using babel-jest, here is an example of using @babel/core:

    custom-transformer.js

    1. 'use strict';
    2. const {transform} = require('@babel/core');
    3. const jestPreset = require('babel-preset-jest');
    4. module.exports = {
    5. process(src, filename) {
    6. const result = transform(src, {
    7. filename,
    8. presets: [jestPreset],
    9. });
    10. return result || src;
    11. },
    12. };

    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"}.

    1. const babelJest = require('babel-jest');
    2. module.exports = babelJest.createTransformer({
    3. });

    See dedicated docs for more details.