It’s nearly impossible to keep up with the pace JavaScript frameworks pop up nowadays and I believe it’s good to have some focus. For me, this means the big three as defined in this post and more specifically doing basic TDD with React.

Prerequisites

This blog post assumes the following has been installed on your machine.

  • NodeJS (LINK)
  • Any source code editor of your liking. I am using Visual Studio code (LINK)

Result

You will have the following in place at the end of this post:

  • A simple React application with two components using the create-react-app node package
  • Unit tests
    • Smoke testing
    • Isolated (shallow) testing
    • Snapshot testing
    • Integrated testing
  • Code coverage

The resulting project can be found HERE.

Setup a React application

There is a great Github repo “create-react-app”, which can be run using npx. The great thing about npx is that it doesn’t download and install the create-react-app package globally. This package is used as a scaffolding tool in order to create the React application. We don’t want it to be there after the application has been created and we always want to create the application with the latest version of the scaffolding tool. See NPX for more features.
To do this, specify the following command:

C:\Users\username\temp>npx create-react-app my-app

The folder structure will then look as follows:

The application comes with some default functionality and is a good starting point for a TDD approach. Test files can be both named with the .text or .spec postfix.

Smoke testing

Let’s open App.test.js:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
  ReactDOM.unmountComponentAtNode(div);
});

At the top, React is needed as the front end JS library. “ReactDOM” contains methods regarding the DOM that can be used at the top level of a React application. The third import statement makes sure that the “App” component is available for testing.

There is a default smoke test that basically renders the App component inside a div element and assumes no errors occur during the rendering. The unmountComponentAtNode is needed to clean up the DOM and remove the div and everything below. This method returns true if the component has been successfully unmounted so it can be wrapped in an expect statement as follows:

ReactDOM should only be used for functionality, not for testing purposes. I added it because the team from “create react app” includes it in the test file. The general advice is to use React test renderer or Enzyme. The statement from the team on this can be found here.

When running the tests (through the “npm test” command), the following is shown:

In Visual Studio Code, the Jest extension by Orta is very useful as it shows you (among many other features) which tests have failed inside the test files.

For example (notice the red dot before the test and the comment after the “expect”):

Applying TDD

Let’s create some unit tests for a simple button. This button should be a React component of which the title can be set.

I used this React style guide to set up the folder structure.

Shallow testing

From the previous section, we know how we can create a smoke test using React Dom. Now, let’s do some shallow testing. Shallow testing is used to test components in isolation. This means that if component A renders HTML, component B, and component C, that we only want to test the HTML. Not the nested components B and C. To test those, there should be shallow tests of those components (B.test.js and C.test.js). As we will see later, integrated component testing is useful when we want to test A, B, and C.

Enzyme is a great library to do shallow testing and we need to install it first:
npm install --save-dev enzyme enzyme-adapter-react-16

We have to create a new file in order to set up the enzyme React adapter for our project.

In the package.json file we need to include the setupTests.js file:

"setupFiles": [
    "./src/setupTests.js"
  ]

The shallow test in Button.test.js is defined as follows:

import React from 'react';
import Button from './Button';
import { shallow } from 'enzyme';

it('should render without crashing', () => {
    shallow(<Button />);
});

Enzyme is used to render a shallow of the Button component. This means that if the Button would have other React children components, these would not be fully rendered. Shallow testing is useful to create isolated tests.

If we run npm test, the following error is shown:

Let’s create the functionality of the button:

If you have npm test still running, upon saving the Button.js file, the tests will automatically rerun and pass:

There is a line in the test report that mentions zero snapshots. In the next section, I will explain snapshot testing.

Snapshot testing

Snapshot testing is a technique that verifies the integrity of rendered components. This means that the output HTML is watched for changes and a warning is displayed if changes are made. When a snapshot test is written, the output HTML is written to a file (a snapshot is taken) and is used as a baseline. After subsequent edits on the component, the edits will be compared against the snapshot file and warnings are displayed if there are differences.

import renderer from 'react-test-renderer';
it('should match the snapshot', () => {
    const component = renderer.create(
        <Button />
      );
      let tree = component.toJSON();
      expect(tree).toMatchSnapshot();
  });

When the tests are run, a snapshot is created:

If we would change the text of the button from “OK” to “Not OK”, the following will happen:

The newly rendered button does not match the snapshot anymore and we are notified by this.

Integrated testing

To demonstrate integrated testing, we want another component to communicate with the Button.
Let’s illustrate this:

The button will need to be enriched with a text property so that a parent component can pass this dynamically:

In Button.test.js:

it('should display the correct text', () => {
    const wrapper = shallow(<Button text="Hit me"/>);
    const buttonText = 'Hit me';
    expect(wrapper.contains(buttonText)).toEqual(true);
});

Let’s change the render function of the button:

render() {
        return (
            <button>{this.props.text || 'OK'}</button>
        );
    }

This allows the button to render text passed as a property by another component or (by default) display “OK”. The tests should pass.
Next, the App component will contain the Button component and pass the text “Press” to it. We expect the html to contain the Button with the text “Press”:

At the top of App.test.js:

import { mount } from 'enzyme';

The render unit test:

it('should render the button correctly', () => {
  const wrapper = mount(<App />);
  const buttonText = 'Press';
  expect(wrapper.contains(buttonText)).toEqual(true);
});

Notice the “mount” keyword here. As opposed to “shallow” it fully renders the component (including child components). The test will fail so let’s add functionality to App.js that makes it pass.

At the top:

import Button from './components/Button';

The render function:

return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
        <div>
          <Button text="Press"/>
        </div>
      </div>
    );

Passing tests:

Code coverage

The code coverage report can be generated by the command “npm test — –coverage”;

This is a good example of when code coverage does not tell you as much as you would like. The App and Button are covered 100% by one of the three rendering function (ReactDOM, React test renderer or Enzyme mount). This triggers the render function of the component and since this is all code in it, it will report 100%.

Result

When we run the application, the result is as follows:

ReactDOM, React test renderer and Enzyme

For me it was confusing to know when to use ReactDOM, the react test renderer and Enzyme.

  1. ReactDOM (LINK)
    1. Used to write functionality, not for testing
    2. Interact with the DOM directly, so a DOM is needed
  2. React test renderer (LINK)
    1. Used to write unit tests
    2. Used to do shallow (LINK), snapshot and integrated testing
  3. Enzyme (LINK)
    1. Used to write unit tests
    2. Used to do shallow, snapshot and integrated testing
    3. Contains a lot of helper functions that make for example DOM manipulation easier
    4. Uses the React test renderer

From a  TDD with React perspective, the React test renderer and Shallow renderer would have sufficed. I still believe it is good to add Enzyme at an early stage since most projects will be more complex then this example. Having a testing utility ready in advance eases development. I am not sure why the spec file that comes with the create react app uses the ReactDOM. In my opinion it should use the React test renderer or Enzyme (mount).

Summary

In this blog post, I explained how to do basic TDD for React. I’ve shown different testing techniques (Shallow, snapshot and integrated testing) and how these can contribute to a more robust application. I believe that this robustness is crucial in the ever-growing JavaScript ecosystem.

The project can be found HERE.
If you would like to have an overview of the ecosystem and increase your web development focus, check out this cool roadmap for 2018.