Jest is a testing framework, built on top of Jasmine, with added features such as parallel execution, zero startup configuration, snapshot testing capability and extended capabilities like more matchers for expect, mock, spies.
Let us see how can we get started with testing React components using Jest
If you have created the React Application using Create React App, then jest comes preinstalled. The below instructions are for the custom React Application development, which do not have Jest pre installed.
In order to install Jest, add the Jest package in development dependency.
npm install jest --save-dev
If you are using yarn, then run the command
yarn add jest --save-dev
Now that you have jest installed, add the script for test, so that it is easier to test.
{
....
"scripts": {
"test": "jest"
}
....
}
By adding the above script, you can easily run the test cases.
npm test
We are going to use the sample application created using Create React App. You can browse the project at github (master branch).
We are going to write a simple React component which has following functionality.
Below is the component called App.js
import React, { Component, Fragment } from 'react';
class App extends Component{
state = {
warning: false
}
showWarning = () => {
this.setState({
warning: true
})
}
render(){
const { warning } = this.state;
return (
<Fragment>
{
warning ? <span data-testid="warning-text">This is warning text</span> : null
}
<button data-testid="warning-button" onClick={this.showWarning}>Show Warning</button>
</Fragment>
)
}
}
export default App;
Create React App comes with React testing library by default. React testing library is a helper library, that makes it easier to write the test case for React components.
Create a file called App.test.js
, and it will be picked up by default by Jest for testing. As mentioned above, we are going to write test case for two main scenarios
Here is the App.test.js
file
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('do not show warning by default', () => {
const wrapper = render(<App />);
expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(0);
})
We have imported the render
method from React testing library. The render
method returns a object that contains various helper methods to query on the DOM.
We get the reference to the render using the following code
const wrapper = render(<App />);
Then we check that warning text should not be shown by default. There are many helper methods available like query
, find
and get
. We are going to use the queryAllByTestId
method, which will allow you to query the DOM using the testid
attribute. It returns the array of the matching dom elements.
expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(0);
So we have seen how to test the default (or initial) scenario. Let us test the second scenario
test('show warning on click of button', () => {
const wrapper = render(<App />);
wrapper.getByTestId('warning-button').click();
expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(1);
})
In the above test case, we render the App
component just like the first test case. Then we get the button element and click on it to show the warning message.
wrapper.getByTestId('warning-button').click();
On click of the button, the warning message will be shown. So we are going to test it by checking that the length of the arrat of matching element is 1.
expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(1);
Till now, we have used Jest only to run the test cases. Jest provides much more functionality to test the applications. One of the major feature is mocking and spies. Jest allows you to mock the components which are not required to be tested (Jest is framework agnostic, but for this tutorial, we are going to focus only on the React component).
Now go to branch mock
.
https://github.com/we-techdomain/testing-react-component-jest/tree/mock
Here we have moved the warning message to a separate component called Warning
. In this tutorial, we are going to demonstrate
App
component which contains the child component Warning
by mocking the child component. We are going to test
Warning
component should not be visibleWarning
component should be visibleThis is the code for App.js
component.
import React, { Component, Fragment } from 'react';
import Warning from './Warning';
class App extends Component{
state = {
warning: false
}
showWarning = () => {
this.setState({
warning: true
})
}
render(){
const { warning } = this.state;
return (
<Fragment>
{
warning ? <Warning/> : null
}
<button data-testid="warning-button" onClick={this.showWarning}>Show Warning</button>
</Fragment>
)
}
}
export default App;
Notice the below line, here we have replaced the warning div from previous tutorial with the Warning
component.
{
warning ? <Warning/> : null
}
In order to test the above component, we will have to mock the Warning
component. We can test the component without mocking Warning
component also, but it will be brittle test. Because we are unit testing App
component, its test case should not be dependant upon the implementation of Warning
component. We have to try write test case in such a way that if implementation of child component changes, the parent component test cases do not fail. That will make the test case robust.
Jest provides methods to mock the components. We are going to go indepth into mocking the modules, but will show a sample of one way of manual mocking the ES6 modules.
In order to mock the Warning
component, we are going to use the manual mocks. That means that we are going to provide our own simple and consice mock implementation of it. In order to mock the component, jest provides the following method
jest.mock("modulepath", mockFunction)
Our module path is going to be ./Warning
, because the Warning
component is present at that path relative to the test case file.
In the mock function, we are going to use jest.fn().mockImplementation
to mock the implementation of Warning
component. The code is going to be
jest.mock('./Warning', () => {
return jest.fn().mockImplementation(() =>
<div data-testid="warning-text">Mock Warning Component</div>);
})
Inside the mockImplementation function, we are passing a custom mock React component with the data-testid
attached, so that we can use the same test cases from the previous scenario (first unit test case).
Due to this, the other test cases are same as first unit test case.
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
jest.mock('./Warning', () => {
return jest.fn().mockImplementation(() =>
<div data-testid="warning-text">Mock Warning Component</div>);
})
test('do not show warning by default', () => {
const wrapper = render(<App />);
expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(0);
})
test('show warning on click of button', () => {
const wrapper = render(<App />);
wrapper.getByTestId('warning-button').click();
expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(1);
})
The mock functions are automatically hoiseted on top, so there is no difference by importing App component before or after the mock code. Importing App
after the mock will result in eslint error (import/first), so its better to import App
first.
We have timer functions inside our code. Suppose that we have a timeout of 10 seconds in our code, then writing the test case which will wait for 10 seconds to do the assertion will result in delay of finishing the test case.
Thankfully, Jest has a solution for this. It provides fake timers, which can be used to speed upon the time. Refer to the tutorial below to learn more.
How to unit test the timer functions – setTimeout and setInterval in React using jest
We already have detailed tutorial to understand the mocking of async code like http calls. Please refer to the tutorials below.
Axios
is a popular library that is used to make the http calls. It is possible to use jest
to mock this library also. This guide will serve as an example on how to mock any 3rd party library module