Evan Cordulack

Unit Testing React with Jest and Enzyme Notes

March 27, 2020

Pull Request that shows some examples using paragraphs.cordulack.com

Overview

Jest: Multi-featured test runner (it can run coverage reports, set up test doubles, run assertions)

Enzyme: Tests the output of your components

Setting up Jest and Enzyme with Create React App

Jest should already be working after you set up your application using create-react-app

If you are using React 16 and set up your app using create-react-app, you don't need a jest.config.js to point jest at your setupTests.js file. Create React App will find it automatically: https://create-react-app.dev/docs/running-tests#initializing-test-environment

setupTests.js (in your src directory)

// setup file
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

Import what you need from enzyme at the top of your test files import { mount } from 'enzyme';

Troubleshooting

  • I ran into this error when I first tried to use Enzyme's instructions for setting it up with Jest: SyntaxError: Cannot use import statement outside a module, while you can troubleshoot this, I found that running npm run test instead of jest cleared up the problem. It looks like create-react-app had already everything set up.

Jest: How do you organize tests effectively?

Mocha using describe() to nest tests into groups. What is the convention in Jest?

"To create tests, add it() (or test()) blocks with the name of the test and its code. You may optionally wrap them in describe() blocks for logical grouping but this is neither required nor recommended." (Running Tests, Create React App Documentation, https://create-react-app.dev/docs/running-tests#writing-tests)

If you use it(), you can easily exclude tests https://create-react-app.dev/docs/running-tests/#focusing-and-excluding-tests Skip: rename it() to xit() Just run one (or focus) a test: rename it() to fit()

Jest: Assuming you can group tests, does Jest have anything like before, after, beforeEach, afterEach, for helping in setting up and tearing down?

Sure does: https://jestjs.io/docs/en/setup-teardown

Jest: Coverage Reports when using create-react-app

Output your test coverage to the terminal by running your tests with npm run test -- --coverage https://create-react-app.dev/docs/running-tests#coverage-reporting

Jest: Snapshots

https://jestjs.io/docs/en/snapshot-testing Jest allows you to take a "snapshot" of a rendered component. You can compare the output your tests generate to the snapshot to see if anything changed in your UI that shouldn't have. The snap shot gets created the first time you run your test and subsequent tests compare the results to the snapshot.

"A typical snapshot test case for a mobile app renders a UI component, takes a snapshot, then compares it to a reference snapshot file stored alongside the test. The test will fail if the two snapshots do not match: either the change is unexpected, or the reference snapshot needs to be updated to the new version of the UI component." ("Snapshot Testing", Jest Docs, https://jestjs.io/docs/en/snapshot-testing.)

“One of the most challenging aspects of my project was keeping the input and output files for each test case in sync. Each little change in functionality could cause all the output files to change. With snapshot testing I do not need the output files, the snapshots are generated for free by Jest! I can simply inspect how Jest updates the snapshots rather than making the changes manually.” – Kyle Davis working on fjs. (Kyle Davis in "Jest 14.0: React Tree Snapshot Testing" https://jestjs.io/blog/2016/07/27/jest-14.html, July 27, 2016)

"The snapshot artifact should be committed alongside code changes, and reviewed as part of your code review process. Jest uses pretty-format to make snapshots human-readable during code review. On subsequent test runs Jest will compare the rendered output with the previous snapshot. If they match, the test will pass. If they don't match, either the test runner found a bug in your code (in this case, it's component) that should be fixed, or the implementation has changed and the snapshot needs to be updated." ("Snapshot Testing, Jest Docs, https://jestjs.io/docs/en/snapshot-testing)

Nice article about Snapshot Testing: https://benmccormick.org/2016/09/19/testing-with-jest-snapshots-first-impressions/ https://egghead.io/lessons/javascript-use-jest-s-snapshot-testing-feature

Enzyme: Shallow, Mount, and Render

shallow(): will render a component without any of its child components https://enzymejs.github.io/enzyme/docs/api/shallow.html

mount(): render a component and all child components then put them into the DOM. Call .unmount() after your test to clean it up. This allows you to interact with your component in your tests via JS methods. (Kingsley Silas, "Writing Tests for React Applications Using Jest and Enzyme", https://css-tricks.com/writing-tests-for-react-applications-using-jest-and-enzyme/, 2019)

render(): render a component and child components as HTML (doesn't put them into the DOM) (Kingsley Silas, "Writing Tests for React Applications Using Jest and Enzyme", https://css-tricks.com/writing-tests-for-react-applications-using-jest-and-enzyme/, 2019)

Enzyme: Testing Function Components & What To Do About React Hooks

It appears that if you use class-based React components, you can create an instance of your component using shallow() and access class methods after calling instance().

"useEffect() and useLayoutEffect() don't get called in the React shallow renderer."

Explanation from Dan Abramov: Use mount() and mock child components instead of using shallow(). "Generally we've been using shallow renderer less and less at Facebook because it encourages brittle tests that verify implementation detail. So components that relied on it a lot introduced a lot of refactoring friction, and didn't catch valuable regressions anyway for us. (Your experience might differ.)

The main value proposition of shallow rendering is that you mock out the inner components. We see the value in this, but in our experience doing this at the module system level gives you more leeway when refactoring. For example, you can always jest.mock('Button', () => 'button') to replace a child Button component with a mock. We've mostly been happy with this approach and found it more flexible."

Enzyme: Simulating Events

https://enzymejs.github.io/enzyme/docs/api/ReactWrapper/simulate.html

Enzyme: Debugging using console.log()

If you try to use console.log() to log some output from your wrapper, you will get something like this ReactWrapper {}. Enzyme has a helper function for outputting the string you are probably after.

console.log(thingYouWantToSee.debug());

https://enzymejs.github.io/enzyme/docs/api/ShallowWrapper/debug.html