Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
August 25, 2020 10:30 am GMT

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.

Lets 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. https://jestjs.io/docs/en/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.
https://enzymejs.github.io/enzyme/

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

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

Install Enzyme:

npm install --save-dev enzyme enzyme-adapter-react-16 enzyme-to-json

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;

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() });

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;

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

Lets 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: '',};

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}/>

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,    });  };

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;

Our add item button should be updated as the following

<button  className='ui button'  type='submit'  disabled={submitDisabled}>

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}

The addItem function should be as the following

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

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>

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;

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

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, well 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);  });});

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, lets verify everything works. SaveApp.test.js and run the test command from the console using the following command:

npm test

Next, lets 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);  });

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, its sufficient to just assert that the button is on the page.We can use Enzymes containsMatchingElement() method. This will check if anything in the components output looks like the expected element.

We dont have to match attribute-for attribute using the containsMatchingElement() method.

Next, well 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);  });

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);  });

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, theres 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);  });});

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 wegroup 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. Well 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);    });  });

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);  });});

We used beforeEach to simulate a change event again, this time setting value to a blank string.Well 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.

Next, well simulate the user submitting the form.

Simulating a form submission

After the user has submitted the form, Well 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 well 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`', () => {      });    });

Our beforeEach will simulate a form submission. Recall that addItem expects an object that has a method preventDefault().
Well 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);});

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, lets assert that the item is inside the table.

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

Next, well 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('');});

Finally, well assert that the button is again disabled:

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

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);      });    });  });});

Now, we can verify that all our tests pass.

Conclusion

In total, so far weve 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 for offering this tutorial to us. WrapPixel offers high quality free and premium React Templates, Do check them out.


Original Link: https://dev.to/suniljoshi19/unit-testing-react-components-using-enzyme-and-jest-testing-frameworks-19c3

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To