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.
To perform a basic Jest test, we only need very minimal npm
dependencies.
{
"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.
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.
export const Add = (i, j) => {
return i + j;
};
I also created the "math.test.js" file to test the "Add()
" function.
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:
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:
npm run test-w
After the run, we can see that Jest performed the unit test and we have the expected result.
The Jest & React
With the minimal Jest example in mind, we are ready to explore the unit tests on React applications:
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:
npm run pack
You can start the node server by the following command:
node server
You can then access the React application by "http://localhost:3000/".
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.
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.
"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".
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.
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.
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.
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 />);
expect(wrapper.html().length).toBeGreaterThan(0);
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);
addButton.simulate('click', { preventDefault: () => {} });
addButton.simulate('click', { preventDefault: () => {} });
expect(wrapper.state('numbers').length).toEqual(2);
expect(wrapper.find('.ItemList').children().length).toEqual(2);
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:
npm run test
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