Kajol Singh

Unit Test Case in React for Beginners

By Kajol Singh

Last updated cal_iconDecember 15, 2021

Unit testing is a level of software testing where individual units/components of the software are tested. This means testing an individual React Component or pure functions in the React world.

Why should I test?

Unit testing, in particular, is possibly an essential part of testing. The best scenario about testing is that, for example, if I am a developer and writing a component and for some reason, another developer starts working on this, then the test suite which I have written for this will fail, so this will show that another person is working on this. 

  1. They runs fast, allowing us to understand if anything is broken in our app quickly.
  2. They bring great value with the least effort as they are easy to write than other more complicated tests.
  3. If they fail, it is easy to know where the error is because they only concentrate on small units of code.
Requirements for React unit testing

To do our unit testing, we will use two prevalent libraries 

  1. Jest 
  2. Enzyme
Introducing Jest

Jest is a node-based test runner allowing fast parallel running of tests in a node environment. Written by Facebook and has become very popular in the past few years (You could also use other libraries such as mocha or chai). 

Introducing Enzyme

The enzyme will help us render React components in testing mode. You will see both in action in the following sections.

Installation And configuration

1. you can install Jest with npm or yarn:

yarn add –dev jest
#or
npm install –save-dev jest

2 . Install Enzyme:
npm install –save-dev enzyme enzyme-adapter-react-16 enzyme-to-json
3 . Create a src/setupTests.js file to customize the Jest environment:
import Enzyme from ‘enzyme’;
import Adapter from ‘enzyme-adapter-react-16’;

// Configure Enzyme with React 16 adapter
Enzyme.configure({ adapter: new Adapter() });

// If you’re using the fetch API
import fetch from ‘node-fetch’;
global.fetch = fetch;

4 . In order to run jest with npm test, then update your package.json like this:

{
“dependencies”: {
“react”: “16.8.3”,
“react-dom”: “16.8.3”
},
“devDependencies”: {
“enzyme”: “3.9.0”,
“enzyme-adapter-react-16”: “1.11.2”,
“jest”: “24.6.0”,
“node-fetch”: “2.6.0”,
“react-test-renderer”: “16.8.6”
},
“scripts”: {
“test”: “jest”,
“test:watch”: “jest –watch”,
“test:coverage”: “jest –coverage”
},
“jest”: {
“setupFilesAfterEnv”: [“<rootDir>/src/setupTests.js”]
}
}

  1. Now, create a jest.config.js file inside the root directory and follow the link given below:

https://jestjs.io/docs/en/configuration

Please find the sample of jest config file mentioned below :

module.exports = {
verbose: true,
collectCoverageFrom: [
‘src/**/*.{js,jsx}’, ],
setupTestFrameworkScriptFile: “./enzyme.js”,
roots: [ “../__tests__” ], modulePaths: [ “./__stubs__” ],
coveragePathIgnorePatterns: [
‘src/apollo-client’,
],
moduleFileExtensions: [
‘web.js’,
‘Js’,
etc…
],
testPathIgnorePatterns: [
‘/node_modules/’,
etc…
],
moduleNameMapper: { “.scss$”: “scss-stub.js” }
}
6 . how to run the test file ?
// Single run
$ npm run test

// Watchmode for the coverage
$ npm run test:c

Type of Unit test cases
  1.  Snapshot testing in Jest
  2. Snapshots are ideal for testing things that you don’t expect to change or don’t want to change in the future. We’ll use them to test components, actions, and reducers and ensure they only change when we want them too.
  3. And the advantage of this testing is it will increase your coverage percentage and also, when something changes in the code structure, it will break the snapshot so that you come to know that there are some changes in the code.

How to test with snapshots

Step 1. Write a test for the component and in the expect block, use .toMatchSnapshot() method that creates Snapshot itself.

it(‘renders correctly enzyme’, () => {
const wrapper = shallow(<Basic />)
expect(toJson(wrapper)).toMatchSnapshot(); });

Step 2. When you run the test for the first time on the one level, along with the test, there will be a created directory named __snapshots__ with the autogenerated file inside with the extension.snap.

Step 3. Push snapshot into the repository and store it along with the test.

If a component has been changed, you need to update the snapshot with —updateSnapshot flag or the shot form u flag.

  1. Testing in Jest and Enzyme

Asynchronous testing has the following parameters mentioned below :

Mount
  • Full DOM rendering including child components
  • Ideal for use cases where you have components that may interact with DOM API or use React lifecycle methods to fully test the component
  • As it mounts the component in the DOM .unmount() should be called after each test to stop tests affecting each other
  • Allows access to both props directly passed into the root component (including default props) and props passed into child components.
  • With the help of the Mount, we can do the innermost nested component rendering example (hoc)
  • If we are using a mount, then .childAt() will be used in that case for rendering the innermost HOC 
Shallow
  • Renders only the single component, not including its children. This is useful to isolate the component for pure unit testing. It protects against changes or bugs in a child component, altering the behavior or output of the component under test
  • As of Enzyme 3 shallow components do have access to lifecycle methods by default
  • Cannot access props passed into the root component (therefore also not default props), but can those passed into child components and test the effect of props passed into the root component. This is as with shallow(<MyComponent />), you’re testing what MyComponent renders – not the element you passed into shallow
  • When we are using shallow, we have to use .dive().       
Render
  • Renders to static HTML, including children
  • Does not have access to React lifecycle methods
  • Less costly than mount but provides less functionality
Start Using Jest

Jest discovers test files within your project via their filenames, which can be located at any depth of your project. There are two naming conventions we can adopt for Jest to pick up our tests:

  • Any file with a .test.js suffix or a .spec.js suffix. This may be preferable when an App.test.js file can accompany a component (e.g., App.js) in the same directory, 

Where they reside next to each other optimizes discoverability and keeps import statements to a minimum.

  • Any .js file within __tests__ folders throughout your project. If you have multiple test files to test a particular component or component directory, a __tests__ folder allows a more coherent structure whereby your tests and components are not mixed. This may be preferable in larger projects.

Which method you adopt is your call and will depend on your project.

Now let’s write a simple test to examine Jest syntax. Let’s add an App.test.js file within the same directory as App.js. This test will have no relation to App.js just yet, but instead will introduce some key methods of Jest; describe() and it(), as well as the expect() methods:

// App.test.js

describe(‘Examining the syntax of Jest tests’, () => {
it(‘sums numbers’, () => {
expect(1 + 2).toEqual(3);
expect(2 + 2).toEqual(4);
}); });

Let’s break down the above example to understand this syntax:

  • describe(): An optional method to wrap a group of tests with. describe() allows us to write some text that explains the nature of the group of tests conducted within it. As you can see in the Terminal, the describe() text acts as a header before the test results are shown.
  • it(): Similar to describe(), it() allows us to write some text describing what a test should successfully achieve. You may see that the test() method is used instead of it() throughout the Jest documentation and vice-versa in the Create React App documentation. Both are valid methods.
  • expect() and .toEqual(): Here we carry out the test itself. The expect() method carries a result of a function, and toEqual(), in this case, carries a value that expect() should match.
Testing Terminology

At this point, it is worth going over the main terminology we encounter with React testing — let’s go over some commonly used terms:

Unit test: Testing one isolated function or one React component. Enzyme’s shallow() is a unit test.

Integration test: Testing a multitude of functions working together or an entire React component, including children components. Enzyme’s mount() is an integration test.

Mock function: Redefining a function specifically for a test to generate a result. E.g., returning hard-coded data instead of relying on fetch requests or database calls. This strategy could prevent the hard-coded sum issue we were discussing earlier!

Mock functions can be defined in jest with jest.fn(() => { //function here });.

Main instructions for component testing
  1. One component should have only one snapshot. If one snapshot fails, most likely the others will fail too, so do not create and store a bunch of unnecessary snapshots clogging the space and confusing developers who will read your tests after you. Of course, there are exceptions when you need to test the behavior of a component in two states;
  2. Testing props: As a rule, I divide the testing of the props into two tests: — Firstly, check the render of default prop values; when the component is rendered, I expect a value to be equal from defaultPropsin case this prop has defaultProps. – Secondly, check the custom value of the prop; I set my value and expect it to be received after the render of the component.
  3. Testing data types: To test what type of data comes in the props or what kind of data is obtained after certain actions, I ensure the proptypes are correct. The data type is an essential programming part and shouldn’t be skipped.
  4. Event testing: After creating a snapshot and covering props with tests, you can be sure in the correct rendering of the component, but this is not enough for full coverage if you have events in the component. You can check events in several ways:
  • mock event => simulate it => expect event was called
  • mock event => simulate event with params => expect event was called with passed params
  • pass necessary props => render component => simulate event => expect a certain behavior on called event
  1. Testing conditions: You can often have conditions for the output of a particular class, rendering a certain section of the code, transferring the required props, and so on. Do not forget about this because only one branch will pass the test with default values, while the second one will remain untested. In complex components with calculations and many conditions, you can miss some branches. To make sure tests cover all parts of the code, use a test coverage tool and visually check which branches are covered and which are not.
  2. States’ testing: To check state, in most cases, it is necessary to write two tests:
  • The first one checks the current state.
  • The second one checks the state after calling the event. Render component => call function directly in the test => check how the state has changed. To call a function of the component, you need to get an instance of the component and only then call its methods.

After you understand this list of instructions, your component will be covered from 90 to 100%. I leave 10% for special cases described in the article but can occur in the code.

Examples of Testing

Let’s move to examples and cover components with tests under described structure step by step.

  1. Testing of a component button.js

Take one component from the component directory; let it be button.js

const AddButton = (props) => {
const { children, classes, handleClick } = props;
return (
<div>
<Button className={classes.addBtn} id=”add” onClick={handleClick} {…props}> {children} </Button>
</div> );};
export default withStyles(style)(AddButton);

1. Create snapshot first:

it(‘render correctly date component’, () => {
const buttonWrapper=shallow(<AddButtom />).dive();

2. Testing props:

expect(buttonWrapper.prop(‘children’)).toBe(‘addbtn’);

Test null prop value; this check is required to ensure the component is rendered without defined value:

expect(buttonWrapper.prop(‘children’)).toBe(‘addbtn’);

 Test null prop value; this check is required to ensure the component is rendered without defined value:

it(‘render button correctly with null value’, () => {
const props = {
children: null
},
buttonWrapper= shallow(<AddButton {…props} />);
expect((buttonWrapper).prop(‘children’)).toEqual(null);
});

3.Test proptypes for value, expected to be string:

it(‘render button input correctly with value’, () => {
const props = {
children: ‘addbtn’,
},
buttonWrapper= shallow(<AddButton {…props} />); expect((buttonWrapper).prop(‘children’)).toEqual(‘addbtn’);
});

  1. Test events:

Check the onClick event, for that mock onClick callback => render button component => then simulate click event with new target value => and finally check that onClick event have been called with new value.

it(‘check the onClick callback’, () => {
const props = {
children: ‘addbtn’,
onClick: () => { },
},
buttonWrapper= shallow(<AddButton {…props} />);
const addbtn = wrapper.find(‘#add’);
expect(addbtn.length).toBe(1);
addbtn.simulate(‘click’ });

Note : There are two scenarios for finding the instance of a component

  1.  If we are using the class component, then we can find the instance()
  2. If we are using the functional component, then we will not find the instance() of a component then, in that case, we use a debug() function 

Note:  Writing the test case of the Class component is quite easy than the Functional component because there we didn’t find the instance()

In the next step, we did a Snapshot testing which I mentioned above very clearly 

Now have a look at the coverage part 

  1. Testing of a component with HOC and graphql query
Unit test case of graph ql query scheduleList.test.js

class ScheduleList extends Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
const { datastream_id, cmsContent, schedulesString = ‘ ‘ } = this.props;
return (
<Query fetchPolicy=”network-only” query={GET_DATASTREAM} variables={{ dataStreamId: datastream_id }}>
{({ error, loading, data }) => {
let datastreamList = {};
let requiredFields = {};
if (loading) {

return <Loader />; }
if (error) {
return ‘Error while loading data’;
} if (data) {
const { getDataStreamById } = data;
datastreamList = getDataStreamById.data.schedule;
requiredFields = {
interval: schedulesString,
lookback: `${datastreamList.lookback_window} ${datastreamList.lookback_type}` } }
const scheduleView = cmsContent.ScheduleView;
return ( <>
{
Object.keys(requiredFields).map(key => (
<Stack mb={1}>
<Box pr={5} width={[2 / 3]}>
<Paragraph>{scheduleView[key]}</Paragraph>
</Box>

<Box pr={5} width={[2 / 3]}>
<Paragraph>{requiredFields[key]}</Paragraph>
</Box>
</Stack>))} </> ) }} </Query>
)}}
ScheduleList.propTypes = {
cmsContent: PropTypes.object.isRequired,
datastream_id: PropTypes.string.isRequired,
schedulesString: PropTypes.string.isRequired,
}
export default withCMS(ScheduleViewCms)(withApollo(ScheduleList));
scheduleList.test.js
import { MockedProvider } from ‘@apollo/react-testing’;
import Adapter from ‘enzyme-adapter-react-16’;
import ApolloClient from ‘apollo-boost’;
import { ApolloProvider } from ‘react-apollo’;
import { CmsContext } from ‘@dentsu/platform-shell’;
import { SchedulingForm } from ‘../SchedulingForm’;
import { GET_DATASTREAM_SCHEDULE } from ‘../graphql/Query’;
configure({ adapter: new Adapter() });
let wrapper
const client = new ApolloClient({ uri: ‘http://localhost:5000/graphql’});
const val = [];
const mocks = [{
request: {
query: GET_DATASTREAM_SCHEDULE,
variables: {
dataStreamId: ‘534734773476’
}}
result: {
data: {
getDataStreamSchedule: {
data: {
id: ’23’,
name: ‘Test’,
next_run: ‘Tomorrow’,
last_fetch: ‘Yesterday’,
enabled: false,
schedule: {
id: ‘123’,
cron_preset: ‘Daily’,
cron_start_of_day: ’08/12/2019′,
cron_interval_start: ’30 min’,
time_range_preset_label: ’30 mins’,
time_range_preset: ‘1 hours’,
fixed_start: ‘Today’,
fixed_end: ‘Tomorrow’ } }} } }];
const value = {
data: {
getContent: {
result: {}} },
loading: true };
describe(‘test cases for SchedulingForm’, () => {
const props = {
size: 10,
className: ”,
match: {
params: {
id: ‘13236357637’ } },
cmsContent: {
dst_heading: ‘Data Sources, },
timeRangeCheck: true,
client: {
mutate: async () => { } } };

beforeAll(() => {
wrapper = mount(
<ApolloProvider client={client}>
<CmsContext.Provider value={value}>
<MockedProvider addTypename={false} mock={mocks}>
<SchedulingForm {…props} />
</MockedProvider>
</CmsContext.Provider>
</ApolloProvider> ).childAt(0).childAt(0) .childAt(0) .childAt(0) .childAt(0) });
it(‘should match the snapshot’, () => {
expect(wrapper).toMatchSnapshot()} });
it(‘SchedulingForm function handleDialogue ‘, () => {wrapper.instance().handleDialogue(val)
});
it(‘SchedulingForm handleDateChange function’, () => {
const type = ‘start’,
const date = new Date(‘2020-03-27’);
wrapper.instance().handleDateChange(date, type)
});
it(‘SchedulingForm handleDeleteSchedule function’, () => { wrapper.instance().handleDeleteSchedule(val, val) });

it(‘Simulate mutation onchange’, async () =>
const fn = wrapper
.find(‘DialogBox’)
.at(0)
.prop(‘onChange’);
expect(fn).toBeInstanceOf(Function);
});

Followings are the guidelines

  •  As we have a query, then our first step is to make a mokeData of the query 
  • As we are wrapping in apollo, then we have to create an instance of a client 
  • Now define all the props which are used in the component
  • Now we can wrap the component in the HOC as we can see in the component there are Cms content and withApollo and also we have mock data so we can also wrap it inside mock provider
  • We have applied childAt(0) because we are using mount not shallow
  • After that, we start with the Snapshot testing 
  • Now we have to test the functionality so we can use an instance of the component to test the following component
  • For handlechange or handleclick function, we create a simulate method for the fake click to test the functionality

 

 

Get In Touch

How Can We Help ?

We make your product happen. Our dynamic, robust and scalable solutions help you drive value at the greatest speed in the market

We specialize in full-stack software & web app development with a key focus on JavaScript, Kubernetes and Microservices
Your path to drive 360° value starts from here
Enhance your market & geographic reach by partnering with NodeXperts