Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

A Note on Jest & React

0.00/5 (No votes)
25 Sep 2018CPOL4 min read 6.9K   51  
This is a note on Jest & React

Introduction

This is a note on Jest & React.

Background

The unit test on JavaScript applications has long been a difficult subject, but Jest has changed it. It allows us to perform test on client side JavaScript programs without actually launching a browser instance. In this note, I prepared two examples. One of the examples is a minimal Jest example, the other one is to test React applications.

The Minimal Jest Example

To better understand Jest, I started with a minimal example.

Image 1

To perform a basic Jest test, we only need very minimal npm dependencies.

JavaScript
{
  "name": "min-jest",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "test": "jest",
    "test-w": "jest --watch"
  },
  "devDependencies": {
    "jest": "20.0.4",
    "babel-preset-env": "1.7.0"
  },
  "babel": {
    "presets": [ "env" ]
  }
}
  • All the dependencies to perform a basic test are included by the "jest" dependency;
  • To use ES6, we need the "babel-preset-env" dependency.

The configurations of Jest can be added to the "package.json" file, it can also be included in a file named "jest.config.js" file.

JavaScript
let config = {
  "verbose": false,
    "testMatch": [ "<rootDir>/src/**/?(*.)(test).{js}" ],
    "collectCoverage": true,
    "coverageReporters": [ "html" ],
    "collectCoverageFrom": [ "src/**/*.{js}" ],
    "coveragePathIgnorePatterns": [],
    "coverageThreshold": {
        "global": {
            "branches": 50, "functions": 50,
            "lines": 50, "statements": 50
        }
    }
};
    
module.exports = config;

We have many Jest configuration entries. To start at the minimum, I added the following configurations in the "jest.config.js" file.

  • The testMatch - Where to find the test files
  • The collectCoverage - Collect code coverage status or not
  • The coverageReporters - The format of the code coverage report
  • The collectCoverageFrom - The JavaScript files that the code coverage includes
  • The coveragePathIgnorePatterns - The JavaScript files that the code coverage ignores
  • The coverageThreshold - If the code coverage does not meet the thresholds, Jest will fail the tests

These are all that we need for the Jest environment. To validate the Jest environment, I created a JavaScript file "math.js" to perform the addition of two numbers.

JavaScript
export const Add = (i, j) => {
    return i + j;
};

I also created the "math.test.js" file to test the "Add()" function.

JavaScript
import {Add} from './math';
    
describe('A describe', () => {
    it('Sum should be correct', () => {
        expect(Add(1, 2)).toEqual(3);
    });
});

To run Jest, we can issue the following command:

JavaScript
npm run test

To set a watcher and run the unit tests automatically whenever we save the related files, we can issue the following command:

JavaScript
npm run test-w

After the run, we can see that Jest performed the unit test and we have the expected result.

Image 2

The Jest & React

With the minimal Jest example in mind, we are ready to explore the unit tests on React applications:

Image 3

The React Application

The attached example is the same application that I created in my earlier note without using Redux. I use Webpack to bundle the application. This note is not about Webpack, I will not spend a lot time on it. You can take a look at my earlier note if interested. You can use the following command to pack the React application:

JavaScript
npm run pack

You can start the node server by the following command:

JavaScript
node server

You can then access the React application by "http://localhost:3000/".

Image 4

The example application has two buttons. One of the buttons adds a random integer to the list, the other button clears the list. All the functions are implemented in the React component in the "App.js" file.

JavaScript
import React from 'react';
    
class App extends React.Component {
    constructor(props) {
        super(props);
        
        this.state = {
                numbers: []
        }
        
        this.addARandomNumber = this.addARandomNumber.bind(this);
        this.clearNumbers = this.clearNumbers.bind(this);
    }
    
    addARandomNumber(e) {
        e.preventDefault();
        
        let number = Math.floor((Math.random() * 100) + 1);
        let numbers = [...this.state.numbers, number];
        
        this.setState({numbers: numbers});
    }
    
    clearNumbers(e) {
        e.preventDefault();
        this.setState({numbers: []});
    }
    
  render() {
      let numbers = this.state.numbers;
      
    return <div className="container">
        <div>
                <button onClick={this.addARandomNumber}>Add a random number</button>
                <button onClick={this.clearNumbers}>Clear the numbers</button>
                
                <div>
                    <div className="ItemList">
                    {numbers.map((item, i) =>
                        <div key={i}>No.{i + 1} - {item}</div>
                    )}
                    </div>
                </div>
                
                <div className="ItemAggregator">
                    Total SUM - {numbers.reduce((a, b) => a + b, 0)}
                </div>
            </div>
    </div>;
  }
}
    
export default App;

The Jest & Environment & Configurations & React

Beyond all the dependencies need by React and Webpack, we need to add the following dependencies in the "package.json" to use Jest on React applications.

JavaScript
"jest": "20.0.4",
"babel-jest": "20.0.3",
"enzyme": "3.6.0",
"enzyme-adapter-react-16": "1.5.0"

We also need to extend the Jest configuration to add some React specific configurations in the "jest.config.js".

JavaScript
let config = {
  "verbose": false,
    "testMatch": [ "<rootDir>/client/test/**/?(*.)(test).{js}" ],
    "collectCoverage": true,
    "coverageReporters": [ "html" ],
    "collectCoverageFrom": [ "client/src/**/*.{js}" ],
    "coveragePathIgnorePatterns": [
    "<rootDir>/client/src/index.js",
    "<rootDir>/client/src/polyfills.js"
    ],
    "coverageThreshold": {
        "global": { "branches": 50, "functions": 50, "lines": 50, "statements": 50 }
    },
    "setupFiles": [ "<rootDir>/client/test/jest/polyfills.js" ],
    "testEnvironment": "node",
    "testURL": "http://localhost",
  "transform": {
    "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
    "^.+\\.css$": "<rootDir>/client/test/jest/cssTransform.js",
    "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/client/test/jest/fileTransform.js"
  },
  "transformIgnorePatterns": [ "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$" ],
  "moduleNameMapper": { "^react-native$": "react-native-web" },
  "moduleFileExtensions": [ "web.js", "js", "json", "web.jsx", "jsx", "node", "mjs" ]
};
    
module.exports = config;

The "enzyme" package plays an important role in testing React applications, but we cannot use enzyme directly with some versions of React (* See credit) without an adapter. We need to create an "enzyme.js" file and import enzyme related objects into the test JavaScript files from it.

JavaScript
import Enzyme, { configure, shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
    
configure({ adapter: new Adapter() });
export { shallow, mount, render };
export default Enzyme;

The Unit Tests

With the Jest & enzyme environment ready, I created a unit test "Render.test.js" to make sure the application renders well in the DOM.

JavaScript
import React from 'react';
import ReactDOM from 'react-dom';
import App from '../src/components/App';
    
describe('Test the App Renders fine', () => {
    it('Renders without crashing', () => {
      const div = document.createElement('div');
      ReactDOM.render(<App />, div);
      ReactDOM.unmountComponentAtNode(div);
    });
});

I also created a unit test "App.test.js" to test the "App" component in detail.

JavaScript
import React from 'react';
import { shallow } from './jest/enzyme';
import App from '../src/components/App';
    
describe('Test App component', () => {
    
    it('The component can be rendered', () => {
        const wrapper = shallow(<App />);
        
        // Should expect some HTML
        expect(wrapper.html().length).toBeGreaterThan(0);
        
        // The initial numbers list should be empty
        expect(wrapper.state('numbers').length).toEqual(0);
    });
    
    it ('Test the events', () => {
        const wrapper = shallow(<App />);
        let addButton = wrapper.find('button').at(0);
        let clearButton = wrapper.find('button').at(1);
        
        // Click the add button twice - two entries should be added
        addButton.simulate('click', { preventDefault: () => {} });
        addButton.simulate('click', { preventDefault: () => {} });
        expect(wrapper.state('numbers').length).toEqual(2);
        expect(wrapper.find('.ItemList').children().length).toEqual(2);
        
        // Click the clear button - number entries should be cleared
        clearButton.simulate('click', { preventDefault: () => {} });
        expect(wrapper.state('numbers').length).toEqual(0);
        expect(wrapper.find('.ItemList').children().length).toEqual(0);
    });
});

With the "shallow" function from enzyme, we can have very detailed tests on the React component. In this test program, I simulated the button clicks and checked the list of the numbers and the HTML contents. To run the unit tests, you can issue the following command:

test
npm run test

Image 5

We can see that all the expectations in the unit tests are met and the coverage is 100% without launching a browser instance.

Points of Interest

  • This is a note on Jest & React;
  • I hope you like my postings and I hope this note can help you one way or the other.

History

  • 9/24/2018: First revision

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)