Unit Testing React, D3 with Enzyme and Jest

Successive Digital
Successive Digital
Published in
10 min readSep 17, 2019
React Testing

Introduction: — This blog is for how to test React component with Enzyme and Jest. In this blog, here we are discussing how to test react components which have d3 content with the help of Enzyme and Jest. This blog helps you to write test cases and also help you to choose the correct testing framework according to your requirement.

In this blog, I am going to cover some of the testing frameworks which are showing below.

  1. Jest
  2. Jasmine
  3. Enzyme

Jest

Jest is an open-source test framework created by Facebook that has great integration with React.js. It is a very powerful testing framework. The most known feature of jest is the snapshot. It is very helpful for testing.

Features of Jest:-

  1. Zero Configuration
  2. Fast and sandboxed
  3. Extensible Framework
  4. Codemods
  5. Snapshot

To know more about please go through with the below mention link:-

https://jestjs.io/docs/en/getting-started

Jasmine

Jasmine is one of the popular JavaScript unit testing frameworks which is capable of testing synchronous and asynchronous JavaScript code. It is used in BDD (behavior-driven development) programming which focuses more on the business value than on the technical details.

Features of Jasmine:-

  1. Jasmine does not depend on any other JavaScript framework.
  2. Jasmine does not require any DOM.
  3. All the syntax used in Jasmine framework is clean and obvious.
  4. Jasmine is heavily influenced by Rspec, JS Spec, and Jspec.
  5. Jasmine is an open-source framework and easily available in different versions like the stand-alone, ruby gem, Node.js, etc.

Suite Block:- Jasmine is a testing framework for JavaScript. A suite is the basic building block of Jasmine framework. The collection of similar type test cases written for a specific file or function is known as one suite. It contains two other blocks, one is “Describe()” and another one is “It()”.

One Suite block can have only two parameters, one “name of that suite” and another “Function declaration” that actually makes a call to our unit functionality that is to be tested.

Example:-

describe(“Hello World”, function () {it(“should Return Hello world”, function () {expect(HelloWorld()).toEqual(‘Hello World’);});});

Skip Block:- Jasmine also allows the developers to skip one or more than one test cases. These techniques can be applied at the Spec level or the Suite level. Depending on the level of application, this block can be called as a Skipping Spec and Skipping Suite respectively .we will skip a specific Spec or Suite using “x” character.

Example:-

describe(“Hello World”, function () {xit(“should Return Hello world”, function () {expect(HelloWorld()).toEqual(‘Hello World’);});});

Before And After Functions:- There are few before and after functions that are supported by jasmine.

  1. BeforeAll
  2. BeforeEach
  3. AfterAll
  4. AfterEach

BeforeAll:- Runs a function before any of the tests in this file run. If the function returns a promise or is a generator, Jest waits for that promise to resolve before running tests.

Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before aborting. Note: The default timeout is 5 seconds.

Example :-

beforeAll((done) => {
document.body.appendChild(element);
barGraph = ReactDOM.render(
<BarGraph
configuration={config.verticalNumber}
data={config.data}
/>,
element
)
setTimeout(() => {
done();
}, 1000);
});

BeforeEach:- Runs a function before each of the tests in this file runs. If the function returns a promise or is a generator, Jest waits for that promise to resolve before running the test.

Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before aborting.

Example :-

beforeEach( async () => {
if(global.ZFB_DOWNLOAD_INSTANCE) {
await clickByIdAfterWait(global.ZFB_DOWNLOAD_INSTANCE);
}
});

AfterAll:- Runs a function after all the tests in this file have completed. If the function returns a promise or is a generator, Jest waits for that promise to resolve before continuing.

Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before aborting.

Example :-

afterAll(‘do logout’, async () => { 
await clickByIdAfterWait(‘logout’);
});

AfterEach:- Runs a function after each one of the tests in this file completes. If the function returns a promise or is a generator, Jest waits for that promise to resolve before continuing.

Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before aborting.

Example:-

afterEach( async () => {
await wait(300);
await clickByIdAfterWait(‘editor-button-policygroups’);
await wait(300);
await selectEntities(vportPG, entity, [global.PG_INSTANCE_ID_2]);
});

Spies:- Jasmine spy is another functionality that does the exact same as its name specifies. It will allow you to spy on your application function calls. There are two types of spying technology available in Jasmine. The first methodology can be implemented by using spyOn() and the second methodology can be implemented using createSpy().

Example :-

spyOn(self.mockService, ‘fetch’).and.callThrough();VSDServiceTest.makeRequest = jasmine.createSpy(“makeRequest”).and.callFake(() => {
return new Promise((resolve, reject) => {
return resolve(‘Done’);
})
});

Enzyme

The enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components’ output.

Enzyme’s API is meant to be intuitive and flexible by mimicking jQuery’s API for DOM manipulation and traversal.

Before Installing enzyme please keep these below key points

  1. If you are using react 16.X than you need to install enzyme-adapter-react-16 and enzyme 3.X
  2. If you are using react 15.X than you need to install enzyme-adapter-react-15 and enzyme 2.X

Url:- https://airbnb.io/enzyme/docs/installation/

Running Enzyme Tests:- Enzyme is un-opinionated regarding which test runner or assertion library you use, and should be compatible with all major test runners and assertion libraries out there. The documentation and examples for enzyme use mocha and chai, but you should be able to extrapolate to your framework of choice. If you are interested in using enzyme with custom assertions and convenience functions for testing your React components, you can consider using:

This is some overview of the testing framework. Now the time for the main topic.

Testing React and D3 content with the enzyme

It is very difficult to test d3 content with react because enzyme or any other testing framework only render the render method of React. When I was looking for the solution to this problem. I read many of the documents some are supporting d3 testing with jasmine without React and some documents are for jest and with enzyme for React component testing. For testing both react and d3 together I have found one approach. I have rendered the dom with the enzyme but in the enzyme rendering, I am not able to fetch the d3 content. To fetch the d3 content I have used .html() method which provides the HTML of the current component. I have found the HTML but now the question is how to manipulate the HTML and how to test both d3 and React content. After a lot of searches, I found one manipulating library which is Cheerio. It is just like the jquery library. With the help of this, I am able to test together d3 and React. This summary of how I have test React and d3 together. Now I am going to talk in detail how everything works for me.

Issues:- To test React and d3 together it is very complicated. I have faced many issues while testing d3 and React together some of the important issues I am covering here.

  1. The main issue is that d3 content is not rendering with Enzyme.
  2. Find the d3 content with .html() now the problem how to manipulate that html().
  3. The transition is used in the d3.
  4. How to perform click event
  5. How to call React lifecycle methods.

These are some major challenges that I have faced while I am testing the react and d3 together with enzyme and jest.

Solutions

  • The main issue is that d3 content is not rendering with Enzyme.

Solution:- To render content I have use Enzyme mount method. Mount is used for full dom rendering. In my case, there is one problem which is the d3 content because we are using d3 content with the react content. I am using Enzyme mount with the help of the mount() method I am able to get the React component but I am not able to get the d3 content because the d3 content is mount with the render after the react mounting is done. After a lot of searching, I have found one solution to this problem. I have first got the HTML of the mounted dom and i am able to get complete HTML of the dom.

Example : -

import PieGraph from ‘./index’;
import React from ‘react’;
import { mount } from ‘enzyme’;
describe(“PieGrpah”, () => {
let config;
beforeAll(async () => {
config = await getDataAndConfig(‘PieGraph’);
});
it(“OtherOption by Number”, () => {
const pieGraph = mount(
<PieGraph
width={500}
height={500}
configuration={config.withoutOtherOption}
data={config.data}>
</PieGraph>
);
const html = pieGraph.find(‘svg’).html();
});
});

With the help of .html() method, I am able to get the d3 content but Now I have another problem that is shown below.

  • Find the d3 content with .html() now the problem how to manipulate that HTML.

Solution:- Now the problem is how to manipulate the HTML component to resolve the problem i have found one library that is Cheerio. The Cheerio is used for manipulating the HTML data. Cheerio is mainly used for manipulating the HTML content. It provides many cool methods. The Cheerio parses markup and provides an API for traversing/manipulating the resulting data structure.

To know more about cheerio please go through the below link.

https://www.npmjs.com/package/cheerio

Example:-

pieGraph.test.jsimport PieGraph from ‘./index’;
import React from ‘react’;
import { mount } from ‘enzyme’;
import { getDataAndConfig, getHtml, checkSvg } from ‘./helper’;
describe(“PieGrpah”, () => {
let config;
beforeAll(async () => {
config = await getDataAndConfig(‘PieGraph’);
});
describe(“OtherOption by Percentage”, () => {
let pieGraph, $;
beforeAll(async () => {
pieGraph = mount(
<PieGraph
width={500}
height={500}
configuration={config.percentage}
data={config.data}>
</PieGraph>
);
$ = getHtml(pieGraph, ‘svg’);
});
it(“SVG Dimensions”, () => {
const result = checkSvg(pieGraph);
expect(result).toBeTruthy();
});
});
});
Helper.jsconst cheerio = require(‘cheerio’);export const checkSvg = (component) => {
const $ = getHtml(component, ‘svg’);
const svgHeight = $(‘svg’).attr(‘height’);
const svgWidth = $(‘svg’).attr(‘width’);
return svgHeight == “500” && svgWidth == “500”;
}
export const getHtml = (component, tag) => {
const cheerioData = cheerio.load(component.find(tag).html());
return cheerioData;
}

With help of cheerio, I was able to test most of the graph and also able to cover most of the cases but when I was testing the bar graph I was not able to get the HTML. After digging a lot, I was able to get to know what is the problem. The problem is mention below.

  • The transition is used in the d3.

Solution:- In some cases, they were using d3 transition for animation. Now the problem was that the d3 content was not ready at the time of mounting the component. So d3 content is not available into the HTML. This is a big problem to solve this problem I have used setTimeout. In the timeout section, I have updated the component. After updating the component I am able to solve this problem. I am able to get d3 content.

Example: -

barGraph.test.jsimport BarGraph from ‘./index’;
import React from ‘react’;
import { mount } from ‘enzyme’;
import { getDataAndConfig, getHtml, checkSvg } from ‘./helper’;
describe(“Bar Graph”, () => {
let config;
beforeAll(async () => {
config = await getDataAndConfig(‘DynamicBarGraph’);
});
describe(“Graph with Brush”, () => {
let horizontalBrush, $;
beforeAll(async (done) => {
horizontalBrush = mount(
<BarGraph
width={500}
height={500}
configuration={config.horizontalBrush}
data={config.data}>
</BarGraph>
);
/* Delayed added because the bar is rendered with 300ms animation */
setTimeout(() => {
horizontalBrush.update();
done();
}, 350);
});
it(“SVG Dimensions”, () => {
const result = checkSvg(horizontalBrush);
expect(result).toBeTruthy();
});
});
});
Helper.jsconst cheerio = require(‘cheerio’);export const checkSvg = (component) => {
const $ = getHtml(component, ‘svg’);
const svgHeight = $(‘svg’).attr(‘height’);
const svgWidth = $(‘svg’).attr(‘width’);
return svgHeight == “500” && svgWidth == “500”;
}
export const getHtml = (component, tag) => {
const cheerioData = cheerio.load(component.find(tag).html());
return cheerioData;
}
  • How to perform click event.

Solution:- This is the most complicated problem for me because with the help of cheerio I am able to manipulate the HTML but cheerio is not provided the click event functionality. This is a blocker for me without this testing is incomplete. I had searched a lot then I found a solution. To checking the clickEvent I had use react-dom render. With the help of react-dom render I am able to test the click event. I had also used jest mock function for this.

Example:-

import React from ‘react’;
import { mount } from ‘enzyme’;
import ReactDOM from ‘react-dom’;
import PieGraph from ‘.’;
describe(“PieGrpah”, () => {
let config;
beforeAll(async () => {
config = await getDataAndConfig(‘PieGraph’);
});
describe(“Click Event”, () => {
let pieGraph;
const mockCallBack = jest.fn();
const element = document.createElement(“div”);
beforeAll((done) => {
document.body.appendChild(element);
pieGraph = ReactDOM.render(
<PieGraph
width={500}
height={500}
configuration={config.withoutOtherOption}
data={config.data}
onMarkClick={mockCallBack}
/>,
element
)
setTimeout(() => {
done();
}, 1000);
});
afterAll(() => {
document.body.removeChild(element);
});
it(“Click On PieGraph”, () => {
element.querySelector(‘path’).click();
expect(mockCallBack).toHaveBeenCalled();
});
});
});
  • How to call React lifecycle methods.

Solution:- we all already know that React has some lifecycle methods. All the lifecycle methods have some unique functionality and execution time. Some have been called after mounting and some are called before mounting. In the gauge graph, an animation is applied to the needle. All the work is performing into the componentDidmount method. It is very difficult to find the angel of the needle and the percentage because both things are performing into the componentDidmount method. After searching a lot I have found one solution. I have tested this thing with the help of react-dom-render. With the help of this, I am able to get all the content like needle angle and percentage.

Example:-

import React from ‘react’;
import ReactDOM from ‘react-dom’;
import { getDataAndConfig } from ‘../testHelper’;
import GaugeGraph from ‘.’;
const cheerio = require(‘cheerio’);describe(“GaugeGraph”, () => {
let config;
beforeAll(async () => {
config = await getDataAndConfig(‘GaugeGraph’);
});
describe(“Initial Configurations”, () => {
let gaugeGraph, $;
const element = document.createElement(“div”);
beforeAll((done) => {
document.body.appendChild(element);
gaugeGraph = ReactDOM.render(
<GaugeGraph
width={500}
height={500}
configuration={config.configuration}
data={config.data}
/>,
element
)
setTimeout(() => {
done();
$ = cheerio.load(element.innerHTML);
}, 3000);
});
afterAll(() => {
document.body.removeChild(element);
});
it(“SVG Dimensions”, () => {
const height = $(‘svg’).attr(‘height’);
const width = $(‘svg’).attr(‘width’);
expect(height).toEqual(“500”);
expect(width).toEqual(“500”);
});
});
});

--

--

Successive Digital
Successive Digital

A next-gen digital transformation company that helps enterprises transform business through disruptive strategies & agile deployment of innovative solutions.