Site icon NodeXperts

Unit Test Case in React for Beginners

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
Shallow
Render
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:

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

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:

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:
  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:

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