Matt Arbesfeld CEO @ LogRocket

Testing React Applications (Part 1 of 3)

4 min read 1141

For many years I dreaded front end development. It’s not because I don’t love making products or writing CSS, but rather it was virtually impossible to properly test my UI code. UI code is particularly vulnerable to bugs: the combination of API flakiness, random user inputs, and race conditions make it incredibly easy to make logic errors. For a long time, there were not great tools for verifying the behavior of this code.

With React and the ecosystem of testing tools that have emerged around it, it’s finally possible to build robust, scalable tests that provide strong guarantees on code correctness.

The number of possibilities to test on the front end grows exponentially. User input, network, and race conditions can all introduce bugs.

When we decided to start writing UI tests for our app, we found that there weren’t any great resources that explained the various techniques for React testing. The goal of this series is to discuss these techniques and provide an overview of how to get started.

In modern JavaScript front ends, there are 3 types of tests that we might want to write:

  • Unit tests: these verify the behavior of individual components or modules. There are a number of tools and libraries that make writing these tests easier.
  • Application tests: applications and business logic are hard to fully test with unit tests. Application tests (often called “integration tests”) test your entire application code, often with a mocked-out API.
  • End-to-end tests: if you want to test your entire application (front end and back end), end-to-end tests allow you to make assertions that your entire system works as expected.

In this post, I’ll talk about unit tests and when / how to write them.

Unit Testing

In React, unit tests fall under a few different buckets:

  • Logic tests
  • Component tests
  • Storybook tests

Imagine that we’re building a “calculator” app. Each of these tests would serve a slightly different purpose:

  • Logic tests: does the calculator properly evaluate an equation and return the correct result?
  • Component tests: does a number show up in the calculator which I click a button?
  • Storybook tests: does the calculator look correct when the user is adding two numbers?

Logic tests

Logic tests are the most straightforward unit tests. The simplest case might look something like this:

import { expect } from 'chai';
function addTwoNumbers(a, b) {
   return a + b;
}
describe('addTwoNumbers', () => {
  it('should add two numbers', () => {
     expect(addTwoNumber(2, 3)).to.equal(5);
  });
});

These are usually really quick to write, and are quite useful when working on gnarly code that has a lot of edge cases. It’s important not to go overboard with these tests — you should only write tests for code with a stable API. If the function is constantly changing in purpose or inputs, the guarantees of the test will become meaningless.

To set up logic tests in your front end, I’d recommend using Jest or Mocha, along with Chai to make test assertions:

Component tests

React’s component model is quite convenient for writing tests. Instead of having to test an entire app with an integration test, component tests allow us to test individual components in isolation. Moreover, React’s virtual event model lets us perform “actions” on a component without the browser environment! Here’s an example integration test which uses AirBnB’s enzyme library:

class MyComponent extends Component {
  state = {
    counter: 0,
  };
  
  render() {
     return ( 
       <div>
         {this.state.counter}
         <button onClick={() => this.setState({ counter: this.state.counter + 1 })} />
       </div>
     );
  }
}
describe('MyComponent', () => {
  it('should increment the counter when the button is pressed', () => {
    const wrapper = shallow(<MyComponent />);
    
    expect(wrapper.text()).to.contain(0);
    wrapper.find('button').simulate('click');
    expect(wrapper.text()).to.contain(1);
  });
});

You can play around with enzyme and component testing at this Runkit: https://runkit.com/arbesfeld/enzyme-example

Like logic tests, it’s important to find the correct API boundaries to test. It’s not worth writing tests for components that are constantly changing. Also, simple pure-functional components are not that useful to test: they rarely have bugs or issues. I’d recommend writing component tests when you are building a UI library of complex components, like multi-selects, type-ahead search boxes, etc.

Storybook tests

react-storybook and other “UI Dev Environments” allow you to build components in a self-contained dev environment, and then persist “Stories” of your components which make it easy for other developers to iterate on the component.

Some developers would argue that these are not real tests, but I feel like they have the same purpose as tests. By codifying all of the possible states of a component, it makes it easier for other developers to improve the component, and use the component in other parts of an application.

If you’re interested in react-storybooks, here are a few other good resources for digging in:

Should I write frontend unit tests?

This is a common question that I hear when discussing React engineering. To make this decision there are often a few factors at play:

  • How complex is the component logic? If the test is as long as the component code, it’s probably not worth testing.
  • How likely is the component to change? Is this feature / component going to be evolving over time? If so, it’s probably better to consider integration tests.
  • Do tests help me write the component? Sometimes writing tests before coding a component is actually really helpful in speeding up development.
  • How robust does the product have to be? Depending on the stage of your product, you might have very high reliability guarantees. Unit tests will almost certainly reduce the likelihood of bugs over time, which could be impactful for the business.

Thanks for listening — and happy unit testing!


Plug: LogRocket, a DVR for web apps

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Matt Arbesfeld CEO @ LogRocket

Leave a Reply