How should I unit test my code-Part 2
Examples for testing the React, Redux code using Jest, and Enzyme.
Getting Started
Hi everyone, in this article we will learn to write unit tests for some of the common scenarios in React. If you are new to unit testing, you can check out the article Why should I unit test my code?
If you need help with the setup of Jest and Enzyme before proceeding to write tests, check here.
I have created a repo at GitHub which contains all the setup and examples which we will be going through in this tutorial. Feel free to clone the repo or to refer to it at any point you get stuck while following through the below steps.
To clone the repo, use:
git clone https://github.com/anuk79/UnitTestingReactUsingJestAndEnzyme.git
We will follow the below pattern for each example:
- Code to be tested
- Test cases
- Explanation
So, let’s get started 😎
Example 1: Testing React Component
We will write our first component(LoadingSpinner) with very basic code.
// loading-spinner.jsximport React from 'react';const LoadingSpinner = (props) => {return (
<div className={props.customClassName}>
{'...loading'}
</div>
);
}export default LoadingSpinner;
Here, we have a functional component which renders only a div
with text as …loading
To unit test this, we will use shallow rendering of enzyme which returns a wrapper instance around the rendered output. PFB the test cases:
// loading-spinner.test.jsconst wrapper = shallow(<LoadingSpinner customClassName="test- class"/>);expect(shallowToJson(wrapper)).toMatchSnapshot();
expect(wrapper.find('div').length).toBe(1);
expect(wrapper.find('div').text()).toBe('...loading');
expect(wrapper.find('div').prop('className')).toBe('test-class');
Explanation:
- expect(shallowToJson(wrapper)).toMatchSnapshot();
Here, we match the snapshot generated to the already stored snapshot of the component. Here, the test will fail while running for the first time via commandyarn jest
ornpm run jest
This is because the snapshot has not yet been generated. So, we need to generate the snapshot for the first time, which can be done by appending-u
to the test command ->npm run jest -u
oryarn jest -u
This is useful in validating if the changes done are really intentional or possibly a mistake. - In the following tests, we verify that the wrapper contains one
div
with text as...loading
and it’s className istest-class
which was passed as props to theLoadingSpinner
component.
So, we have successfully written test-case for a very basic component with some props. Let’s now move ahead to our next example with some event listeners
Example 2: Testing event handlers
We will create a component named InputComponent which will render an input element with attributes mapped to the props received.
const InputComponent = (props: Props) => {
return (
<input type={props.type} value={props.value} onChange={event => props.handleChange(event)} />
)
}
PFB test cases for it. We will focus only on testing the handleChange
event
const handleChangeMock = jest.fn();const wrapper = shallow(<InputComponent handleChange={handleChangeMock}/>);
wrapper.find('input').simulate('change', 'testEvent');expect(handleChangeMock).toBeCalledWith('testEvent');
Explanation:
- Here we will use the mocking concept. Since we are unit testing the component, we do not need to know what exactly the method
handleChange
, which has been received as props in our component, does. Instead, we can usejest.fn()
to create a jest mock, which we then pass to the component InputComponent - Now, we use the below line to simulate (or call) the
change
event of the input element.
wrapper.find('input').simulate('change', 'testEvent');
3. Now, the last line will assert if the method received in props (handleChangeMock in our case) was actually called with the testEvent as passed which simulating the change event of the input.
To read more about mocking, refer — https://jestjs.io/docs/en/mock-functions
Also, you can read more about mocking and spying in an awesome article titled Understanding Jest Mocks by Rick Hanlon II
Example 3: Testing asynchronous calls
We have a method here which uses fetch
to asynchronously call an api.
// user.resolver.jsexport const getUserDetails = async () => {
try {
const response = await fetch('/api/get-user-details',
{
'method': 'GET',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'accept': 'application/json'
}
}
);
if (!response.ok) {
throw new Error(response.status);
} else {
return response.json();
}
} catch (error) {
throw new Error(error);
}
}
To test the above method, we will need to mock the fetch
method. PFB the test cases:
// user.resolver.test.jsimport { getUserDetails } from './user.resolver';describe('User resolver', () => {
it('should return result when api call for userDetails succeeds', async () => { const mockResult = { response: 'test response' }; window.fetch = jest.fn().mockImplementation(() => ({
ok: true,
status: 200,
response: 'test response',
json: () => new Promise(resolve => { resolve(mockResult); }) })); const userDetails = await getUserDetails(); expect(userDetails.response).toEqual('test response');});
it('should throw error when api call for userDetails fails', async () => { window.fetch = jest.fn().mockImplementation(() => ({
ok: false,
status: 404
})); try {
await getUserDetails();
} catch (e) {
expect(e.message).toEqual('Error: 404');
}
});});
Explanation:
- Here, we are using jest.fn().mockImplementation which accepts a function that should be used as the implementation of the mock. Since we need to test for both success and failure results for the api call, we will return different values each time while mocking the implementation of the
fetch
method. - We need to mark the test case as
async
to inform the test case that we are performing asynchronous operations inside of it. We do so by prepending theasync
keyword to the test case callback. - Inside the test, we call the method using keyword
await
prepended to it - For the success scenario, we will assert if the response returned is as expected (i.e., the same response as returned from our mock implementation)
- For the error scenario, we will use try-catch so that the error thrown by the actual code is caught in the catch block where we can expect the error message.
To read more about async/await, refer — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
This brings us to the end of this article. Here, we have learned how to test some basic React components, event handlers and asynchronous calls which we use frequently in Redux.
Once again, you can refer to the git repo here to refer to more examples and test cases — https://github.com/anuk79/UnitTestingReactUsingJestAndEnzyme
Note: This article covers the examples for unit testing the React and Redux code. PFB the series topics:
- Why should I unit test my code?
- How should I unit test my code?
2.1. Setup
2.2. Examples (this article) - What should I unit test in my code?
Stay tuned and catch you in the next article !! Any feedback is highly appreciable.
Please drop comments if you are facing issues while testing any specific scenario, and I will be glad to try it out and help :)
Thanks for reading, have a great day!!