preface

GrowingIO Design is a library of components written in React. Essentially, the React component, you can test the React component in the same way you test any other JavaScript code. There are many ways to test the React component. It can be broadly divided into two categories:

  • Render tree of components: Render tree of components in a simplified test environment and do assertion checking on their output.

  • Run the entire application: Run the entire application in a real browser environment.

We call the first category unit testing, and this article focuses on the testing strategy for this case; The second category is called end-to-end testing. Complete end-to-end testing is valuable in preventing multiple regressions to important workflows, and there is a special article on end-to-end testing of components.

Research tools

For the selection of testing tools, the tools listed in the 2020 State of JavaScript Survey were chosen. Let’s start by looking at some of the more common tools for testing the ecosystem from the perspective of “usage” and “satisfaction.”

Test tool usage comparison

Test tool satisfaction comparison

Jest and Storybook both scored high in “usage” and “satisfaction”, and the newly entered Testing Library also scored high in “satisfaction”.

Note:

Satisfaction: Will use again/(will use again + will not use again)

Usage :(will be used again + will not be used again)/total

In addition to the above tools, take a look at the percentage of users using other tools:

Tool selection

React tools (v17.0.2)

  • Jest is a JavaScript test runner. It allows you to manipulate DOM using jsDOM. Although jsDOM is only an approximate simulation of how a browser works, it is usually sufficient for testing React components. Jest has excellent iteration speed and provides several powerful features, such as emulating Modules and Timers to give you finer control over how your code executes.

  • The React Testing Library (RTL) is a set of tools that allow you to test React components without relying on their implementation. It makes refactoring a breeze and pushes you to embrace accessibility best practices. Although it does not allow you to shallowly render a component by omitting child elements, test runners like Jest can do this by mocking you.

They actually recommend another tool on the v16.7.0 website:

  • Enzyme is a JavaScript test utility for React that makes it easier to test the output of the React component. You can also manipulate, traverse, and somehow emulate a runtime for a given output. The Enzyme API is designed to be intuitive and flexible by mimicking the jQuery API for DOM manipulation and traversal.

Jest = Jsdom + Mocha + Sinon + Chai, which shows how powerful Jest is, and it works right out of the box.

We’ve been using unit tests written by Jest + Enzyme since 2018 and beyond, but by 2020, we need to take a second look and choose a tool that works for us right now. After the main RTL comparison, from the history of the first.

A bit of history

  • On March 2, 2016, Airbnb officially announced Enzyme, a JavaScript library used to test the React component. During the previous three months, Enzymes had collected 3,000 Stars on GitHub, 50 contributors, 45 of whom were not Airbnb employees.

  • On March 15, 2018, Kent C. Odds posted on social media that he wanted to make a lighter test library to replace Enzyme. On April 2, Kent C. Odds announced the React Testing Library on his blog, a lighter version of the React Testing Library that integrates react-dom and react-dom/test-utils utility functions. Promote better testing practices.

  • In February 2019, the React team released the long-awaited new version of React (V16.8), which dramatically changed the API and introduced React Hooks. But the Enzyme does not support separate tests for Hooks, while RTL supports separate libraries.

  • On February 28, 2020, Airbnb announced that they had transferred the Enzyme ownership to an outside GitHub organization. While they pledged to continue supporting Enzyme, they also mentioned RTL’s growing popularity in their program.

  • 2021 only one developer currently maintains the Enzyme — Jordan Harband. He is a prolific open source contributor, a member of the TC39 committee (which defines JavaScript), and a true hero, single-handedly maintaining the tool that powers millions of test suites around the world.

npm trends

The figure above shows the download trend of Enzyme and RTL over the past two years. Around September 2020, THE number of DOWNLOADS of RTL exceeded that of the other two years, and then completely disappeared in the following year.

Now it looks as if the industry has switched to RTL.

Talk is cheap, Show me code

Suppose we now have a component like Toggle:

class Toggle extends React.Component { 
  constructor(props) { 
    super(props); 
    this.state = {isToggleOn: true}; 
 
    this.handleClick = this.handleClick.bind(this); 
  } 
 
  handleClick() { 
    this.setState(state => ({ 
      isToggleOn: !state.isToggleOn 
    })); 
  } 
 
  render() { 
    return ( 
      <button onClick={this.handleClick}> 
        {this.state.isToggleOn ? 'ON' : 'OFF'} 
      </button> 
    ); 
  } 
} 
Copy the code

The test case for the Toggle component contains at least two of the following:

  1. When the Toggle is ON, it turns to OFF after clicking.

  2. When the Toggle is in OFF state, it becomes ON state after clicking;

Use Enzyme to do this as follows:

describe("Toggle", () => {
  let component;  
  beforeEach(() => {
    component = mount(<Toggle />);
  });

  it("can turn off when is on", () => {
    component.instance().handleClick();
    expect(component.state().isToggleOn).toEqual(false);
  });
  
  it("can turn on when is off", () => {
    component.setState({ isToggleOn: false });
    component.instance().handleClick();
    expect(component.state().isToggleOn).toEqual(true);
  });
});
Copy the code

Use the Testing Library to do this, as follows:

describe("Toggle", () => {
  it("can turn off/on", async () => {
    const { container } = render(<Toggle />);
    fireEvent.click(container);
    expect(container.innerHTML).toEqual("OFF");
 
    fireEvent.click(container);
    expect(container.innerHTML).toEqual("ON");
  });
});
Copy the code

summary

With RTL, you can easily write tests that are representative of how users will experience your application. For example, when you write tests in RTL, you are testing your application as if you were a user interacting with the application interface.

On the other hand, when you write tests with Enzyme, even if you can achieve the same level of confidence as RTL, building test structures in a similar way to real users is a bit of a hassle.

Some other practices

A snapshot of the test

Snapshot testing is a Jest feature.

Snapshot tests are a very useful tool whenever you want to make sure your UI does not change unexpectedly.

You can use the test renderer to quickly generate serializable values for the React tree, rather than rendering the graphical UI, which may require building the entire application. Look at a specific example:

import React from 'react'; import renderer from 'react-test-renderer'; import Link from '.. /Link.react'; it('renders correctly', () => { const tree = renderer .create(<Link page="https://www.growingio.com">GrowingIO</Link>) .toJSON(); expect(tree).toMatchSnapshot(); });Copy the code

Link component implementation view: github.com/facebook/je…

After running Jest, a snapshot file will be generated like this:

exports[`renders correctly 1`] = ` 
<a 
  className="normal" 
  href="https://www.growingio.com" 
  onMouseEnter={[Function]} 
  onMouseLeave={[Function]} 
> 
  GrowingIO 
</a> 
`; 
Copy the code

Snapshot artifacts should be submitted with code changes and reviewed as part of the code review process.

Here are three best practices from Jest’s website:

  1. Treat snapshots as code

  2. Tests should be deterministic

  3. Use Descriptive snapshot names

Based on our practice in GrowingIO Design, you don’t need to take snapshots for every component and every state of the component. There are two reasons:

  • In most cases, changing a component’s CSS style name or adding or removing Dom elements changes the structure of the component but does not change the look of the component, which requires updating the snapshot and increasing the workload of code review.

  • Snapshot tests can be implemented with just a few lines of code, which significantly improves line-of-code test coverage, but the quality of the tests is not high. It also increases the test command execution time.

Storybook

Documentation tools for components, described in the GrowingIO Design Component Library Building Component Development article, use Storybook to write component demos that can be reused in unit testing tools. Every Demo is “renderable” without relying on Storybook. This means that any test framework will also be able to render the Demo.

Here is an example of how to use it in RTL:

import React from 'react'; import { render, screen } from '@testing-library/react'; import { Primary } from './Button.stories'; it('renders the button in the primary state', () => { render(<Primary {... Primary.args} />); expect(screen.getByRole('button')).toHaveTextContent('Primary'); });Copy the code

conclusion

The most important thing is that RTL focuses on testing the user experience — after all, that’s what really matters. Our customers don’t see what props our components have, or whether they’re arrays or objects, they just care if it works — and will help you deliver value in the future.

The future of React lies in function-based components, React Hooks, asynchronous rendering, and these features are best used with RTL today. Looking at Enzyme’s development over the past three years, it seems unlikely that it can catch up to all these features and solve so many other problems at the same time.

More importantly, using RTL helps improve component library Accessibility (A11Y). Component library accessibility will be covered in detail in a later article.

reference

  1. GrowingIO Design:github.com/growingio/g…

  2. State of JavaScript Survey: 2020.stateofjs.com/zh-Hans/tec…

  3. Testing Overview:reactjs.org/docs/testin…

  4. The Test Utilities: 5 c54aa429e16c80007af3cd2 — reactjs.net lify. App/docs/Test – u…

  5. Enzyme: enzymejs. Making. IO/Enzyme /

  6. Enzyme: JavaScript Testing Utilties for React: medium.com/airbnb-engi…

  7. Enzyme’s Next Phase: medium.com/airbnb-engi…

  8. Jordan HarBand:github.com/ljharb

  9. The Snapshot Testing: jestjs. IO/docs/snapsh…

  10. Storybook Unit Testing:storybook.js.org/docs/react/…

  11. Accessibility: developer.mozilla.org/zh-CN/docs/…