• type: FrontEnd
  • title: Testing React components with Jest and Enzyme
  • Link: hackernoon.com/testing-rea…
  • author: Artem Sapegin

The introduction

Some people say that testing the React component isn’t very useful in general, but I think it’s necessary in a few scenarios:

  • Component library,
  • Open source projects,
  • Integration with third-party components,
  • Bugs, preventing recurrence.

I’ve tried a lot of tool combinations, but in the end, if I can recommend them to another developer, I’d rather recommend the following combination:

  • Jest, a testing framework;
  • Enzyme, React test library;
  • Enzyme -to-json converts the snapshot that the enzyme wrapper type matches Jest

I often use shallow rendering and Jest snapshot testing in my tests.

Snapshot test in Jest

Shallow rendering

Shallow rendering refers to rendering a component as a virtual DOM object, but rendering only the first layer and not all child components. So even if you make a change to a subcomponent it won’t affect the output of the shallow render. Or a bug in an introduced child component will not affect the shallow rendering of the parent component. Shallow rendering is DOM independent.

Here’s an example:

const ButtonWithIcon = ({icon, children}) = > (
    <button><Icon icon={icon} />{children}</button>
);
Copy the code

React will render as follows:

<button>
    <i class="icon icon_coffee"></i>
    Hello Jest!
</button>
Copy the code

But in shallow rendering it will only render as follows:

<button>
    <Icon icon="coffee" />
    Hello Jest!
</button>
Copy the code

Note that the Icon component is not rendered.

A snapshot of the test

A Jest snapshot is like a static UI with a combination of text characters representing Windows and buttons: it is the rendered output of a component stored in a text file.

You can tell Jest which components will output UI without unexpected changes, and Jest will save it to a file like this at runtime:

exports[`test should render a label 1`] = `  `;

exports[`test should render a small label 1`] = `  `;
Copy the code

Each time you make a change to a component, Jest compares the value of the current test and shows the difference, and asks you to update the snapshot if you make a change.

In addition to testing, Jest stores snapshots in files like __snapshots __ / label.spec.js.snap, which you need to submit at the same time.

Why Jest

  • It’s very fast.
  • Snapshot tests can be performed.
  • Interactive monitoring mode tests only the parts that have been modified.
  • The error message is very detailed.
  • The configuration is simple.
  • Supported by Mocks and Spies.
  • You can generate test reports from the command line.
  • The development prospect is very good.
  • Don’t write error-prone assertions like ‘Expect (foo).to.be. A (‘ function’)’ because Jest only writes ‘expect(foo).to.be.true’ thus determines the correct assertion.

Why choose Enzyme

  • Handy tool library package, can handle shallow rendering, static rendering markup and DOM rendering.
  • Jquery-style API, easy to use and intuitive.

configuration

The first step is to install all dependencies including the same version dependencies:

npm install --save-dev jest react-test-renderer enzyme enzyme-adapter-react-16 enzyme-to-json
Copy the code

You also need to install the Babel plug-in babel-jest or TypeScript plug-in TS-jest

Update project package.json file:

"scripts": {
  "test": "jest",
  "test:watch": "jest --watch",
  "test:coverage": "jest --coverage"
},
"jest": {
  "setupFiles": ["./test/jestsetup.js"],
  "snapshotSerializers": ["enzyme-to-json/serializer"]
}
Copy the code

Configuration item ‘snapshotSerializers’ allows you to transfer the enzyme package type to ‘Jest’ snapshot matches by using ‘enzyme-to-json’ without manual conversion.

Create a test/jestsetup.js file to customize the Jest runtime environment (setupFiles configuration item above)

import Enzyme, { shallow, render, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// React 16 Enzyme adapter
Enzyme.configure({ adapter: new Adapter() });
// Make Enzyme functions available in all test files without importing
global.shallow = shallow;
global.render = render;
global.mount = mount;
Copy the code

You can also add the following configuration to package.json for CSS modules

"jest": {
  "moduleNameMapper": {
    "^.+\\.(css|scss)$": "identity-obj-proxy"
  }
}
Copy the code

And run:

Install dependencies at the same time:

npm install --save-dev identity-obj-proxy
Copy the code

Mysql > Identity-obj-proxy Proxies can be played on/around/around/around/around/around/around/around/around/around/around/around/around

Test the rendering of the component

For most components that do not interact, the following test cases are sufficient:

test('render a label', () = > {const wrapper = shallow(
        <Label>Hello Jest!</Label>
    );
    expect(wrapper).toMatchSnapshot();
});

test('render a small label', () = > {const wrapper = shallow(
        <Label small>Hello Jest!</Label>
    );
    expect(wrapper).toMatchSnapshot();
});

test('render a grayish label', () = > {const wrapper = shallow(
        <Label light>Hello Jest!</Label>
    );
    expect(wrapper).toMatchSnapshot();
});
Copy the code

Props test

Sometimes if you want to test more accurately and see real values. In that case you need to use Jest’s assertion in the Enzyme API.

test('render a document title', () = > {constwrapper = shallow( <DocumentTitle title="Events" /> ); expect(wrapper.prop('title')).toEqual('Events'); }); test('render a document title and a parent title', () => { const wrapper = shallow( <DocumentTitle title="Events" parent="Event Radar" /> ); Expect (wrapper. Prop (" title ")). ToEqual (' Events - the Event Radar '); });Copy the code

Sometimes you can’t use snapshots. For example, the component has a random ID like the following code:

test('render a popover with a random ID', () = > {const wrapper = shallow(
        <Popover>Hello Jest!</Popover>
    );
    expect(wrapper.prop('id')).toMatch(/Popover\d+/);
});
Copy the code

Event test

You can simulate events like ‘click’ or ‘change’ and compare components to snapshots:

test('render Markdown in preview mode', () = > {const wrapper = shallow(
        <MarkdownEditor value="*Hello* Jest!" />
    );

    expect(wrapper).toMatchSnapshot();

    wrapper.find('[name="toggle-preview"]').simulate('click');

    expect(wrapper).toMatchSnapshot();
});
Copy the code

Sometimes you want to test how an element in a child component affects the component. You need to use the Enzyme mount method to render a real DOM.

test('open a code editor', () = > {const wrapper = mount(
        <Playground code={code} />
    );

    expect(wrapper.find('.ReactCodeMirror')).toHaveLength(0);

    wrapper.find('button').simulate('click');

    expect(wrapper.find('.ReactCodeMirror')).toHaveLength(1);
});
Copy the code

Test event handling

Similarly in event testing, instead of using the output rendering of the snapshot test component to test the event handler itself with the mock function of Jest:

test('pass a selected value to the onChange handler', () = > {const value = '2';
    const onChange = jest.fn();
    const wrapper = shallow(
        <Select items={ITEMS} onChange={onChange} />
    );

    expect(wrapper).toMatchSnapshot();

        wrapper.find('select').simulate('change', {
        target: { value },
    });

    expect(onChange).toBeCalledWith(value);
});
Copy the code

It’s not just JSX

Jest uses JSON for snapshot testing, so you can test any function that returns JSON in the same way as the test component:

test('accept custom properties', () = > {const wrapper = shallow(
        <Layout
            flexBasis={0}
            flexGrow={1}
            flexShrink={1}
            flexWrap="wrap"
            justifyContent="flex-end"
            alignContent="center"
            alignItems="center"
        />
    );
    expect(wrapper.prop('style')).toMatchSnapshot();
});
Copy the code

Debugging and troubleshooting

Debug shallow renderer output

14. Use Enzyme debug method to print shallow renderer’s output:

const wrapper = shallow(/ * ~ * /);
console.log(wrapper.debug());
Copy the code

Enable failure tests for coverage

When your test fails, the diFF with the coverage flag looks like this:

-<Button
+<Component
Copy the code

Try replacing the arrow function component with a regular function component:

- export default const Button = ({ children }) = >{+export default function Button({ children }) {
Copy the code

RequestAnimationFrame error

When you run your tests, you may see the following error:

console.error node_modules/fbjs/lib/warning.js:42
  Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills
Copy the code

React 16 relies on requestAnimationFrame, so you need to add a polyfill to your test code

// test/jestsetup.js
import 'raf/polyfill';
Copy the code

Reference source

  • Jest cheat sheet
  • Testing React Applications by Max Stoiber
  • Migrating to Jest by Kent C. Dodds
  • Migrating Ava to Jest by Jason Brown