React is one of the most popular libraries and widely used JavaScript tools for developing web apps in the software industry. When building large and scalable applications with React, the development process must have an efficient testing approach, as in Test Driven Development (TDD), a programming practice that instructs developers to write new code if the automated test fails.
There are various testing utilities and frameworks for JavaScript and React applications. In this guide, we will explore testing with Mocha and Enzyme.
Before we dive into the specifics of how to test, we must understand the essential terms used in a test-driven approach.
<Button />
component, the route must change, and the page visit counter must increment.In this section, we will code a straightforward component that will render a "Hello World" title and test it using Mocha.
But first, let's just quickly take a look at what Mocha has to say about itself.
Mocha is a feature-rich JavaScript test framework running on Node.js
and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting while mapping uncaught exceptions to the correct test cases.
Before writing the test, let's install Mocha.
1npm i mocha
Mocha alone is not sufficient as it is a test framework, which expects us to drop in an assertion library of our choice, as the documentation states.
Chai
is one of the most widespread assertion libraries out there. So let's use it in our test script.
1npm i -D chai
You might be wondering what the difference is between a test framework and an assertion library. The answer is that we use a test framework to organize and execute test scripts, whereas an assertion library does the actual verification of the test results.
Now let's start building our component.
1import React from "react";
2
3const App = () => {
4 return <h1>Hello World</h1>;
5};
6
7export default App;
The next bit is very important as we will write our test for the component.
1import React from "react";
2import ReactDOM from "react-dom";
3
4beforeEach(() => {
5 //
6});
7
8afterEach(() => {
9 //
10});
11
12describe("App Component Testing", () => {
13 it("Renders Hello World Title", () => {
14 //
15 });
16});
The beforeEach()
and afterEach()
are test hooks that run before and after every test case in a script. So in the beforeEach()
hook we will be creating a <div>
element to which we are going to mount our component, and in the afterEach()
hook we are going to remove the <div>
element.
1let rootContainer;
2
3beforeEach(() => {
4 rootContainer = document.createElement("div");
5 document.body.appendChild(rootContainer);
6});
7
8afterEach(() => {
9 document.body.removeChild(rootContainer);
10 rootContainer = null;
11});
The describe()
function call structures our test script and groups the individual test cases. It can contain many it()
function calls, which identify individual test cases.
1describe("App Component Testing", () => {
2 it("Renders Hello World Title", () => {
3 // test if the component is rendered and mounted
4 // assert that h1 contains Hello World
5 });
6});
The first thing we have to ensure before adding assertions is whether the component was rendered and the componentDidMount
hook was executed.
React provides a helper function called act()
that ensures updates related to tasks like rendering, events, and async actions have been processed and applied to the DOM.
1import { act } from "react-dom/test-utils";
2// ..
3
4describe("App Component Testing", () => {
5 it("Renders Hello World Title", () => {
6 act(() => {
7 ReactDOM.render(<App />, rootContainer);
8 });
9 // assert that h1 contains Hello World
10 });
11});
Next, we will assess whether the <h1>
element has the text "Hello World"
1describe("App Component Testing", () => {
2 it("Renders Hello World Title", () => {
3 act(() => {
4 ReactDOM.render(<App />, rootContainer);
5 });
6 const h1 = rootContainer.querySelector("h1");
7 expect(h1.textContent).to.equal("Hello World");
8 });
9});
It's time to run our test.
But before that, we need to install babel-register
and few babel plugins as a dev dependency so that we can compile ES6 code in our test to ES5.
1npm i -D babel-register babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0
Create a .babelrc
file in the project root and add the following content.
1{
2 "presets": ["react", "es2015", "stage-0"]
3}
To execute the test we need to run the following command :
1./node_modules/.bin/mocha --require babel-register ./src/App.test.js
Bummer!!
ReferenceError: document is undefined. But why?
Well, the document
object is only available in the browser, and we are running the test in a node
environment. We need to mock the DOM using mocha-jsdom
or any other DOM testing library to execute the test in our console.
1npm i -D mocha-jsdom
We need to declare the global document object before our test code.
1const global.document = jsdom({
2 url: "http://localhost:3000"
3})
Let's now try rerunning our test.
It worked!
What if the test fails? Let's change the title from "Hello World" to "Hello Admin" and check the test results.
You can see how the result is suggesting the expected value. That's very helpful when we are working on a more complex application.
Posting the entire code in this section for your reference, just in case you get stuck following along.
1import React from "react";
2
3const App = () => {
4 return <h1>Hello World</h1>;
5};
6
7export default App;
1import React from "react";
2import ReactDOM from "react-dom";
3import { act } from "react-dom/test-utils";
4import { expect } from "chai";
5var jsdom = require("mocha-jsdom");
6
7global.document = jsdom({
8 url: "http://localhost:3000/"
9});
10
11import App from "./App";
12
13let rootContainer;
14
15beforeEach(() => {
16 rootContainer = document.createElement("div");
17 document.body.appendChild(rootContainer);
18});
19
20afterEach(() => {
21 document.body.removeChild(rootContainer);
22 rootContainer = null;
23});
24
25describe("App Component Testing", () => {
26 it("Renders Hello World Title", () => {
27 act(() => {
28 ReactDOM.render(<App />, rootContainer);
29 });
30 const h1 = rootContainer.querySelector("h1");
31 expect(h1.textContent).to.equal("Hello World");
32 });
33});
We have worked through building a simple component that renders "Hello World" in the title. We looked at how to set up a testing environment using Mocha and how to insert assertions using Chai. These are baby steps in implementing a test-driven approach to your development process. Until next time, keep coding and writing tests.
For more information, check out these resources: