DEV Community

Cover image for Unit Testing React Components Using Enzyme and Jest Testing Frameworks
Sunil Joshi
Sunil Joshi

Posted on • Edited on • Originally published at wrappixel.com

Unit Testing React Components Using Enzyme and Jest Testing Frameworks

In this tutorial, we will be writing unit test for a basic todo application using jest and react.

Let’s get started.


Jest

Jest is a JavaScript testing framework designed to ensure correctness of any JavaScript codebase. It allows you to write tests with an approachable, familiar and feature-rich API that gives you results quickly.
Jest is well-documented, requires little configuration and can be extended to match your requirements. For more information on Jest checkout its official documentation.
Getting Started


Enzyme

Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components' output. You can also manipulate, traverse, and in some ways simulate runtime given the output. For more information checkout Enzyme official documentation.
Getting Started


Setup

In this tutorial we will make use of the create-react-app CLI tool to setting up our project. So go to a directory where you will store this project and type the following in the terminal

create-react-app note-redux-app
Enter fullscreen mode Exit fullscreen mode

If you dont have create-react-app install type the following command in the terminal to install it globally.

npm install -g create-react-app
Enter fullscreen mode Exit fullscreen mode

Sponsored:

React


Install Enzyme:

npm install --save-dev enzyme enzyme-adapter-react-16 enzyme-to-json
Enter fullscreen mode Exit fullscreen mode

The Jest testing framework is by default bundled into create-react-app.

In the src folder, create a tempPolyfills.js file with following content. This is necessary for testing on older browsers.

const raf = global.requestAnimationFrame = (cb) => {
  setTimeout(cb, 0);
};

export default raf;
Enter fullscreen mode Exit fullscreen mode

In the src folder, create a setupTests.js file with following content

import raf from './tempPolyfills'
import Enzyme  from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
Enter fullscreen mode Exit fullscreen mode

For the styling of our todo application, we will make use of the semantic ui library.
in the index.html file of our project, we will add the semantic ui library using the cdn link.

In the app.js file, add the following code snippet

import React from 'react';
class App extends React.Component {
  render() {
    return(
      <div
        className='ui text container'
        id='app'
      >
        <table className='ui selectable structured large table'>
          <thead>
            <tr>
              <th>Items</th>
            </tr>
          </thead>
          <tbody>
            items
          </tbody>
          <tfoot>
            <tr>
              <th>
                <form
                  className='ui form'
                >
                <div className='field'>
                  <input
                    className='prompt'
                    type='text'
                    placeholder='Add item...'
                  />
                </div>
                <button
                  className='ui button'
                  type='submit'
                >
                  Add item
                </button>
                </form>
              </th>
            </tr>
          </tfoot>
        </table>
      </div>
    )
  }
}
export default App;
Enter fullscreen mode Exit fullscreen mode

With this we can view the static version of our todo app.

image

Let’s make our todo app reactive with the following code snippet

First, our todo app needs a state to store the todo items and a todo item.

The following piece of code should be added to app.js

state = {
    items: [],
    item: '',
};
Enter fullscreen mode Exit fullscreen mode

Next we will bind the input to the item property of our state. Hence the input tag in app.js should be updated as follows

<input
    className='prompt'
    type='text'
    placeholder='Add item...'
    value={this.state.item}
    onChange={this.onItemChange}
/>
Enter fullscreen mode Exit fullscreen mode

Since the onChange event is binded to the onItemChange method, in order to update the item property in our state with the value of the input field. The onItemChange method should be as the following:

onItemChange = (e) => {
    this.setState({
      item: e.target.value,
    });
  };
Enter fullscreen mode Exit fullscreen mode

Submitting the Form

If the input field is empty the submit button is disabled. For this feature , add the code snippet below immediately after the render method

const submitDisabled = !this.state.item;
Enter fullscreen mode Exit fullscreen mode

Our add item button should be updated as the following

<button
  className='ui button'
  type='submit'
  disabled={submitDisabled}
>
Enter fullscreen mode Exit fullscreen mode

To submit our todo item, we will add an onSubmit event listener to our form which will trigger the execution of the addItem function.

an onsubmit event should be added to the form tag as the following

onSubmit={this.addItem}
Enter fullscreen mode Exit fullscreen mode

The addItem function should be as the following

addItem = (e) => {
    e.preventDefault();
    this.setState({
      items: this.state.items.concat(
        this.state.item
      ),
      item: '',
    });
  };
Enter fullscreen mode Exit fullscreen mode

Listing all To-Do Items

To list all the todo items we need to iterate over each todo item in the items array.

<tbody>
  {
    this.state.items.map((item, idx) => (
      <tr
        key={idx}
      >
        <td>{item}</td>
      </tr>
    ))
  }
</tbody>
Enter fullscreen mode Exit fullscreen mode

Finally, our todo app should be as the following code snippet.

import React from 'react';
class App extends React.Component {
  state = {
    items: [],
    item: '',
  };
  onItemChange = (e) => {
    this.setState({
      item: e.target.value,
    });
  };
  addItem = (e) => {
    e.preventDefault();
    this.setState({
      items: this.state.items.concat(
        this.state.item
      ),
      item: '',
    });
  };
  render() {
    const submitDisabled = !this.state.item;
    return(
      <div
        className='ui text container'
        id='app'
      >
        <table className='ui selectable structured large table'>
          <thead>
            <tr>
              <th>Items</th>
            </tr>
          </thead>
          <tbody>
            {
              this.state.items.map((item, idx) => (
                <tr
                  key={idx}
                >
                  <td>{item}</td>
                </tr>
              ))
            }
          </tbody>
          <tfoot>
            <tr>
              <th>
                <form
                  className='ui form'
                  onSubmit={this.addItem}
                >
                <div className='field'>
                  <input
                    className='prompt'
                    type='text'
                    placeholder='Add item...'
                    value={this.state.item}
                    onChange={this.onItemChange}
                  />
                </div>
                <button
                  className='ui button'
                  type='submit'
                  disabled={submitDisabled}
                >
                  Add item
                </button>
                </form>
              </th>
            </tr>
          </tfoot>
        </table>
      </div>
    )
  }
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Testing our To-Do App with Jest and Enzyme

create-react-app sets up a dummy test for us in the app.test.js file. Lets execute the initial test for our project with the following command in our project folder.

npm test
Enter fullscreen mode Exit fullscreen mode

image

Open up App.test.js and clear out the file. At the top of that file, we first import the React component that we want to test, import React from react and shallow() from enzyme. The shallow() function will be used to shallow render components during test.

In our first test case, we will assert that our table should render with the header of items. In order to write this assertion, we’ll need to:

• Shallow render the component
• Traverse the virtual DOM, picking out the first th element
• Assert that the th element encloses a text value of “Items”

import App from './App';
import React from 'react';
import { shallow } from 'enzyme';
describe('App', () => {
  it('should have the `th` "Items"', () => {
    const wrapper = shallow(
      <App />
    );
    expect(
      wrapper.contains(<th>Items</th>)
    ).toBe(true);
  });
});
Enter fullscreen mode Exit fullscreen mode

The shallow() function returns what Enzyme calls a “wrapper” object, Shallow Wrapper. This wrapper contains the shallow-rendered component. The wrapper object that Enzyme provides us with has loads of useful methods that we can use to write our assertions. In general, these helper methods help us traverse and select elements on the virtual DOM. One of the helper method is contains(). It is used to assert the presence of an elements on the virtual DOM.

contains()accepts a React Element, in this case JSX representing an HTML element. It returns a boolean, indicating whether or not the rendered component contains that HTML.

With our first Enzyme spec written, let’s verify everything works. SaveApp.test.js and run the test command from the console using the following command:

npm test
Enter fullscreen mode Exit fullscreen mode

Next, let’s assert that the component contains a button element that says “Add item.”

Add the code snippet below after the previous ‘it’ block

it('should have a `button` element', () => {
    const wrapper = shallow(
      <App />
    );
    expect(
      wrapper.containsMatchingElement(
        <button>Add item</button>
      )
    ).toBe(true);
  });
Enter fullscreen mode Exit fullscreen mode

Noticed something new? Instead of using the contains() Enzyme Wrapper method we just used the containsMatchingElement Enzyme Wrapper method. If we use contains, we need to pass contains() a ReactElement that has the exact same set of attributes. But usually this is excessive. For this spec, it’s sufficient to just assert that the button is on the page.We can use Enzyme’s containsMatchingElement() method. This will check if anything in the component’s output looks like the expected element.

We don’t have to match attribute-for attribute using the containsMatchingElement() method.

Next, we’ll assert that the input field is present as well:

it('should have an `input` element', () => {
    const wrapper = shallow(
      <App />
    );
    expect(
      wrapper.containsMatchingElement(
        <input />
      )
    ).toBe(true);
  });
Enter fullscreen mode Exit fullscreen mode

Next, we will assert that the button element is disabled

it('`button` should be disabled', () => {
    const wrapper = shallow(
      <App />
    );
    const button = wrapper.find('button').first();
    expect(
      button.props().disabled
    ).toBe(true);
  });
Enter fullscreen mode Exit fullscreen mode

The find() method is another Enzyme Wrapper method. It expects an Enzyme selector as an argument. The selector in this case is a CSS selector, 'button'. A CSS selector is just one supported type of Enzyme selector. For more info on Enzyme selectors, see the Enzyme docs. We used first to return the first matching element. To read the disabled attribute or any other attribute on the button, we use props(). props() returns an object that specifies either the attributes on an HTML element or the props set on a React component.

Using beforeEach

In all popular JavaScript test frameworks, there’s a function we can use to aid in test setup: beforeEach. beforeEach is a block of code that will run before each it block. We can use this function to render our component before each spec.

At this point, our test suite has some repetitious code. In our previous assertions, we shallow rendered the component in each it block. To avoid these repetitions, we will refactor our assertion. We will just shallow render the component at the top of our describe block:

Our refactored test suit should look like the following

describe('App', () => {
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(
      <App />
    );
  });
  it('should have the `th` "Items"', () => {
    expect(
      wrapper.contains(<th>Items</th>)
    ).toBe(true);
  });
  it('should have a `button` element', () => {
    expect(
      wrapper.containsMatchingElement(
        <button>Add item</button>
      )
    ).toBe(true);
  });
  it('should have an `input` element', () => {
    expect(
      wrapper.containsMatchingElement(
        <input />
      )
    ).toBe(true);
  });
  it('`button` should be disabled', () => {
    const button = wrapper.find('button').first();
    expect(
      button.props().disabled
    ).toBe(true);
  });
});
Enter fullscreen mode Exit fullscreen mode

Testing for User Interactions

The first interaction the user can have with our app is filling out the input field for adding a new item. We will declare another describe block inside of our current one in order to group the test suits for the user interactions. describe blocks are how we“group” specs that all require the same context.

The beforeEach that we write for our inner describe will be run after the before Each declared in the outer context. Therefore, the wrapper will already be shallow rendered by the time this beforeEach runs. As expected, this beforeEach will only be run for it blocks inside our inner describe block

We will use the simulate method to simulate user interactions.

The simulate method accepts two arguments:

  1. The event to simulate (like'change'or'click'). This determines which event handler to use(like onChange or onClick).
  2. The event object (optional)

Notice that in our todo app, when the user has just populated the input field the button is no longer disabled.
So, we can now write specs related to the context where the user has just populated the input field. We’ll write two specs:

That the state property item was updated to match the input field
That the button is no longer disabled

describe('the user populates the input', () => {
    const item = 'Laundry';
    beforeEach(() => {
      const input = wrapper.find('input').first();
      input.simulate('change', {
        target: { value: item }
      })
    });
    it('should update the state property `item`', () => {
      expect(
        wrapper.state().item
      ).toEqual(item);
    });
    it('should enable `button`', () => {
      const button = wrapper.find('button').first();
      expect(
        button.props().disabled
      ).toBe(false);
    });
  });
Enter fullscreen mode Exit fullscreen mode

In the first spec, we used wrapper.state() to grab the state object. We use the state() method which retrieves the state property from the component.In the second, we used props()again to read the disabled attribute on the button.

After the user has filled in the input field, There are two actions the user can take from here that we can write specs for:

  1. The user clears the input field
  2. The user clicks the “Add item” button

Clearing the input field

When the user clears the input field, we expect the button to become disabled again. We will build on our existing context for the describe “the user populates the input” by nesting our new describe inside of it:

describe('and then clears the input', () => {
  beforeEach(() => {
    const input = wrapper.find('input').first();
    input.simulate('change', {
      target: { value: '' }
    })
  });
  it('should disable `button`', () => {
    const button = wrapper.find('button').first();
    expect(
      button.props().disabled
    ).toBe(true);
  });
});
Enter fullscreen mode Exit fullscreen mode

We used beforeEach to simulate a change event again, this time setting value to a blank string.We’ll write one assertion: that the button is disabled again.
When ever the field is empty the button should be disabled.

Now, we can verify that all our tests pass.

image

Next, we’ll simulate the user submitting the form.

Simulating a form submission

After the user has submitted the form, We’ll assert that:

  1. The new item is in state (items)
  2. The new item is inside the rendered table
  3. The input field is empty
  4. The “Add item” button is disabled

So we’ll write our describe block inside “the user populates the input” as a sibling to “and then clears the input”:

describe('and then submits the form', () => {
      beforeEach(() => {
        const form = wrapper.find('form').first();
        form.simulate('submit', {
          preventDefault: () => {},
        });
      });
      it('should add the item to state', () => {

      });
      it('should render the item in the table', () => {

      });
      it('should clear the input field', () => {

      });
      it('should disable `button`', () => {

      });
    });
Enter fullscreen mode Exit fullscreen mode

Our beforeEach will simulate a form submission. Recall that addItem expects an object that has a method preventDefault().
We’ll simulate an event type of submit, passing in an object that has the shape that addItem expects. We will just set preventDefault to an empty function:

With our beforeEach() function in place, we first assert that the new item is in state:

it('should add the item to state', () => {
  expect(
    wrapper.state().items
  ).toContain(item);
});
Enter fullscreen mode Exit fullscreen mode

Jest comes with a few special matchers for working with arrays. We use the matcher toContain()to assert that the array items contains item.

Next, let’s assert that the item is inside the table.

it('should render the item in the table', () => {
  expect(
    wrapper.containsMatchingElement(
      <td>{item}</td>
    )
  ).toBe(true);
});
Enter fullscreen mode Exit fullscreen mode

Next, we’ll assert that the input field has been cleared.

it('should clear the input field', () => {
  const input = wrapper.find('input').first();
  expect(
    input.props().value
  ).toEqual('');
});
Enter fullscreen mode Exit fullscreen mode

Finally, we’ll assert that the button is again disabled:

it('should disable `button`', () => {
  const button = wrapper.find('button').first();
  expect(
    button.props().disabled
  ).toBe(true);
});
Enter fullscreen mode Exit fullscreen mode

Finally, our app.test.js file should contain the following

import App from './App';
import React from 'react';
import { shallow } from 'enzyme';
describe('App', () => {
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(
      <App />
    );
  });
  it('should have the `th` "Items"', () => {
    expect(
      wrapper.contains(<th>Items</th>)
    ).toBe(true);
  });
  it('should have a `button` element', () => {
    expect(
      wrapper.containsMatchingElement(
        <button>Add item</button>
      )
    ).toBe(true);
  });
  it('should have an `input` element', () => {
    expect(
      wrapper.containsMatchingElement(
        <input />
      )
    ).toBe(true);
  });
  it('`button` should be disabled', () => {
    const button = wrapper.find('button').first();
    expect(
      button.props().disabled
    ).toBe(true);
  });
  describe('the user populates the input', () => {
    const item = 'Vancouver';
    beforeEach(() => {
      const input = wrapper.find('input').first();
      input.simulate('change', {
        target: { value: item }
      });
    });
    it('should update the state property `item`', () => {
      expect(
        wrapper.state().item
      ).toEqual(item);
    });
    it('should enable `button`', () => {
      const button = wrapper.find('button').first();
      expect(
        button.props().disabled
      ).toBe(false);
    });
    describe('and then clears the input', () => {
      beforeEach(() => {
        const input = wrapper.find('input').first();
        input.simulate('change', {
          target: { value: '' }
        })
      });
      it('should disable `button`', () => {
        const button = wrapper.find('button').first();
        expect(
          button.props().disabled
        ).toBe(true);
      });
    });
    describe('and then submits the form', () => {
      beforeEach(() => {
        const form = wrapper.find('form').first();
        form.simulate('submit', {
          preventDefault: () => {},
        });
      });
      it('should add the item to state', () => {
        expect(
          wrapper.state().items
        ).toContain(item);
      });
      it('should render the item in the table', () => {
        expect(
          wrapper.containsMatchingElement(
            <td>{item}</td>
          )
        ).toBe(true);
      });
      it('should clear the input field', () => {
        const input = wrapper.find('input').first();
        expect(
          input.props().value
        ).toEqual('');
      });
      it('should disable `button`', () => {
        const button = wrapper.find('button').first();
        expect(
          button.props().disabled
        ).toBe(true);
      });
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Now, we can verify that all our tests pass.

Conclusion

In total, so far we’ve learn how to organize our test code in a behavioral-driven manner, shallow rendering with Enzyme. How to use the shallow Wrapper methods for traversing the virtual DOM, how to use Jest matchers for writing different kinds of assertions ( like toContain() for arrays). Finally, we saw how we can use a behavioral-driven approach to drive the composition of a test suite in react using Jest and Enzyme test frameworks.

We would like to thank WrapPixel and AdminMart for offering this tutorial to us. They offer high quality free and premium React Admin Dashboard and Website Templates.
WrapPixel and AdminMart.

Top comments (0)