← back to the blog


How To Test React Components (hooks, Jest, Enzyme)

Posted on May 4th, 2020 in JavaScript by George

 

Testing is the process of having an extra verification for your app, in order to make sure that the production release works as intended and be as error-tolerant/free as possible. It is the best approach to check that the code is running as intended.

 

For React I use three testing methodologies:

1. Unit testing. Testing an isolated part of your application. For example, shallow rendering of a component to see if it correctly rendered with the default props.

2. Integration testing. Testing different parts of your application to see if they working as expected together. For example, in the child-parent case of two react components, checking the state change is handled correctly.

3. End to end testing. Checking from a user point of view, the application, or a particular phase of the user journey. For example, testing a select item add to cart process, with a headless browser using test cafe or other testing frameworks.

 

In the examples will use Jest and Enzyme for testing.

The examples are proof of concept components for the sake of simplicity.

 

Unit test with 'snapshot' and shallow render

 

In this test, we provide the text as a prop to be rendered.

If you run the test, a snapshot will be created (a new file in __snapshots__ folder)

 

//TopBanner.test.js 

import React from 'react';
import ReactDOM from 'react-dom';
import TopBanner from '../Components/TopBanner';

import Enzyme, {shallow, render , mount} from 'enzyme';
import toJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });


it('TopBanner snap shot test', function() {
  const tc = shallow(<TopBanner title={" Welcome to snapshot test "}/>)

  expect(toJson(tc)).toMatchSnapshot();
});



//TopBanner.jsx

import React from 'react';

const TopBanner = (props) => {
  return (
    <div>
       <h1>{`${props.title ? props.title : 'Title not set'}`}</h1>
       <p> Just text </p>
    </div>
  );
};


export default TopBanner;

 

CLI output:

 

 PASS  src/test/TopBanner.test.js

  ✓ TopBanner snap shot test (8ms)



 › 1 snapshot written.

Snapshot Summary

 › 1 snapshot written from 1 test suite.



Test Suites: 1 passed, 1 total

Tests:       1 passed, 1 total

Snapshots:   1 written, 1 total

Time:        3.344s

Ran all test suites related to changed files.

 

At this point, if you change the text for the prop, the test will fail because the snapshot will not match the new render.

Changing this line of code:

  const tc = shallow(<TopBanner title={" Welcome to snapshot test, this test will fail"}/>)

 

will trigger the following output:

 FAIL  src/test/TopBanner.test.js
  ✕ TopBanner snap shot test (10ms)

  ● TopBanner snap shot test

    expect(received).toMatchSnapshot()

    Snapshot name: `TopBanner snap shot test 1`

    - Snapshot
    + Received

      <div>
        <h1>
    -      Welcome to snapshot test a
    +      Welcome to snapshot test, , this test will fail
        </h1>
        <p>
           Just text 
        </p>
      </div>

      13 |   const tc = shallow(<TopBanner title={" Welcome to snapshot test, , this test will fail"}/>)
      14 | 
    > 15 |   expect(toJson(tc)).toMatchSnapshot();
         |                      ^
      16 | });

      at Object.<anonymous> (src/test/TopBanner.test.js:15:22)

 › 1 snapshot failed.
Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or press `u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        2.829s
Ran all test suites related to changed files.

 

Integration test with hooks and mount render

This test is checking how component WarningText works inside the App parent component which supplies a prop hasError from a hypothetical form completion state change.

// Parent component app App.js

import React, { useState } from 'react';
import WarningText from './Components/WarningText';

function App() {

  const [hasError, setHasError] = useState(true);

  return (
    <div className="App app2">
      <p> Suppose we had a form here which will set hasError to true </p>
      <WarningText error={hasError}  />
    </div>
  );
}

export default App;


/***********************/

// WarningText Component file WariningText.jsx

import React, {useState, useEffect} from 'react';

const WarningText = (props) => {

  const [textState, setTextState] = useState("No issues");

  let changeState = (failCondition) => {
    if(failCondition) 
      setTextState("Please complete the steps above first");
    else
    setTextState("No issue");
  }


  return (
    <div>
       <span><p>warning text: {textState}</p></span>
       <button onClick={()=>{ changeState(props.error)   }}>Submit form</button>
    </div>
  );
};


export default WarningText;



/***********************/

// WarningText test file WarningText.test.js 

import React from 'react';
import ReactDOM from 'react-dom';
import WarningText from '../Components/WarningText';
import {render, fireEvent, cleanup} from '@testing-library/react';
import App from '../App';

afterEach(cleanup)

it('Change the text on click because the form has an error', function() {
  const { getByText } = render(<App><WarningText /></App>);

  expect(getByText(/warning/i).textContent).toBe("warning text: No issues")

  fireEvent.click(getByText('Submit form'))

  expect(getByText(/warning/i).textContent).toBe("warning text: Please complete the steps above first")

});

 

The WarningText component has a prop error that is provided by the parent component App based on its state and own implementation.

The submit button should not be part of the WarningText component but I have added it there for the sake of simplicity.

The test output looks like this now:

 

➜  cramocha git:(master) ✗ ./node_modules/.bin/jest WarningText.test.js
 PASS  src/test/WarningText.test.js
  ✓ Change the text on click because the form has an error (33ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.972s
Ran all test suites matching /WarningText.test.js/i.

 

I hope this is helpful to you.

Thank you and stay safe