© Mohit Thakkar 2020
M. ThakkarBuilding React Apps with Server-Side Renderinghttps://doi.org/10.1007/978-1-4842-5869-9_5

5. Unit Testing Using Jest

Mohit Thakkar1 
(1)
Vadodara, Gujarat, India
 

In previous chapters, we learned how to create web applications using libraries such as React and Next.js. We now know how to develop an application using these libraries. What next?

Once an application is developed, it is important for us to know that it works as expected. To do so, we can write automated unit tests to verify that every component of our application is appropriately doing its job. That is exactly what we are going to learn in this chapter. We will use the Jest framework to perform unit testing on our React application.

Note

As a developer, writing unit tests might seem to be delivering very minimal value while adding a lot of work to your already cramped schedule. However, it will help you reduce your workload in the long run when your application scales up in size, providing a very effective way to detect errors and loopholes in your code.

There are many other JavaScript testing frameworks in the market such as Mocha and Jasmine. However, we will go with the Jest framework due to its increasing popularity and utility. We will learn how to install and set up Jest in our application, then we will create a basic unit test to familiarize ourselves with the concept of Jest, and eventually, we will learn about Matchers and Enzymes that will help us test our React component. Let us start by setting up the Jest framework.

Setting Up Jest

Jest (https://jestjs.io/) is a JavaScript testing framework that we will use in order to test our React application. Let us start by creating our application directory, “jest-testing-app”. Navigate to the directory and execute the “npm init” command to create a package.json file in the directory. Make sure that Node is installed in your system for the command to work. Once successfully executed, you will see a “package.json” file with the following code:
{
  "name": "jest-testing-app",
  "version": "1.0.0",
  "description": "My Jest Application",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "Mohit Thakkar",
  "license": "ISC"
}
The values might differ if you have specified a different set of values during initialization. Since we will be using Jest for testing, make sure that you specify “jest” as the value for the “test” attribute in the “scripts” section. We will now install Jest to our application using the following command:
npm install jest --save
Once installed, you will see the following dependency section added to your “package.json” file:
"dependencies": {
  "jest": "^24.9.0"
}

That’s it. Jest is now successfully installed. Let us write our first test using Jest.

Writing Your First Test Using Jest

Let us first create a simple JavaScript file that contains some basic functions. I have added a file called “functions.js” to the root directory of our application with the following code:

functions.js
const functions = {
    add: (n1, n2) => n1 + n2
}
module.exports = functions

This file contains a simple “add” function that takes in two numbers as input and returns their sum as output. Note that we have used simple JavaScript syntax to export the list of functions as a module. Avoid using ES6 syntax because Jest expects the files to be plain JavaScript while they are imported. Now that we have created a JavaScript function, let us test it using Jest. We will add our test files in the “tests” directory. It is a good practice to name the test file as the same JavaScript file you are testing, with ".test.js" suffix. Consider the following test file that contains the code to test the “add” function:

tests/function.test.js
functions = require('../functions.js')
test('Test Add Function',()=>{
    expect(functions.add(2,3)).toBe(5)
})
That is it. We have created our first test using Jest framework. Let us understand what is happening here:
  • In the code for the test, we simply call the “test()” function that takes in two input parameters – the first one being a description of the test and the second one being the actual test function.

  • In the test function, we use the “expect()” function that takes in the function that we are testing and evaluates it, in our case, the “add()” function.

  • We import the list of functions from "functions.js" using JavaScript’s “require()” method because in order to call the “add()” function, we will have to import it from the file in which it is defined.

  • We use a Matcher, in this case, the “toBe()” function, on the “expect()” function in order to compare the evaluated value with the expected value. We pass the expected value as an input parameter to the Matcher. We will learn more about Matchers in the next topic.

Use the following command to run your tests:
npm test
On successful execution of the command, you will see the summary of test execution in the terminal, as shown in Figure 5-1.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig1_HTML.jpg
Figure 5-1

First Test Using Jest (Success)

Note

It is possible to write multiple tests in a single file.

The “Test Suites” denotes the number of test files, whereas the “Tests” denotes the combined number of tests in those files. In the preceding example, if we change the expected value to something else, let us say “4”, the test execution will fail. Let us try that. Consider the following changes to “function.test.js” file:

tests/function.test.js
functions = require('../functions.js')
test('Test Add Function',()=>{
    expect(functions.add(2,3)).toBe(4)
})
Now, if you run the “npm test” command, you will see in the terminal that the test execution has failed. As shown in Figure 5-2, you will also see the reason due to which the test execution failed, in this case, received value not being equal to the expected value. You will also see both received and expected values in the test execution summary.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig2_HTML.jpg
Figure 5-2

First Test Using Jest (Failure)

Now that we have learned how to write tests for JavaScript functions using Jest, let us delve a little deeper and learn about different Matchers that we can use for testing our code.

Matchers

Matchers are function used by Jest in order to compare the evaluated value with the expected value. In the previous example, we used the “toBe()” Matcher provided by Jest. Note that we use the “expect()” function in order to evaluate the actual value. This function returns an expectation object on which we call our Matcher function to compare it with the expected value. Let us have a look at all the Matchers that are provided by Jest.

Common Matchers

The following are some of the general-purpose Matchers that are used very commonly:
  • toBe(expectedValue) – This is the exact equality matcher. It checks if the value returned by the “expect()” function exactly matches the “expectedValue”.

  • toEqual(expectedValue) – This is similar to the “toBe()” matcher, except the fact that it is used to compare the value of an object. It recursively checks every property of an object.

Let us look at an example in order to understand the common Matchers in working. Consider the following changes to “functions.test.js” file:

tests/functions.test.js
functions = require('../functions.js')
test('toBe Demo',()=>{
  expect(functions.add(2,3)).toBe(5)
})
test('toEqual Demo',()=>{
  var data = {name:'Mohit'}
  data['country'] = 'India'
  expect(data).toEqual({
    name:'Mohit',
    country:'India'
  })
})

To demonstrate the utility of the “toBe()” Matcher, we use the same “add()” function that we tested during the previous example. The function returns “5” and the “toBe()” Matcher asserts it to be true since it is the value that we are expecting.

For “toEqual()” Matcher, we define a new test, “toEqual Demo”. We define a “data” object with one property and then add a new property to the object. We now pass the “data” object to the “expect()” function and use the “toEqual()” Matcher in order to compare it with the expected output. Since both values match, Jest will assert the test to be true. The output of the preceding example should be similar to Figure 5-3.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig3_HTML.jpg
Figure 5-3

Common Matchers in Jest

If you want to try more scenarios, you can change the expected value in the preceding example and notice that the test fails.

Note

You can use the “Jest” extension by “Orts” if you are using Visual Studio Code editor. It provides IntelliSense for Jest and is also very helpful in debugging tests that you write.

Truth Matchers

These are the Matchers that let you check if the evaluated value is null, undefined, defined, true, or false. You need not pass any input parameters to these Matchers:
  • toBeNull() – Matches null values

  • toBeUndefined() – Matches values that are undefined

  • toBeDefined() – Matches values that are not undefined

  • toBeTruthy() – Matches the values that evaluate to true

  • toBeFalsy() – Matches the values that evaluate to false

Let us look at an example in order to understand truth Matchers in working. The following new tests need to be added to the “functions.test.js” file:

tests/functions.test.js
...
test('truth of null', () => {
    const n = null;
    expect(n).toBeNull();
    expect(n).toBeDefined();
    expect(n).not.toBeUndefined();
    expect(n).not.toBeTruthy();
    expect(n).toBeFalsy();
  });
  test('truth of zero', () => {
    const n = 0;
    expect(n).not.toBeNull();
    expect(n).toBeDefined();
    expect(n).not.toBeUndefined();
    expect(n).not.toBeTruthy();
    expect(n).toBeFalsy();
  });
...

We have written two new tests in the preceding example, one to test the truthiness of “null” and the other to check the truthiness of the number zero. The null value should evaluate to null, defined, and not true. On the other hand, the number zero should evaluate to not null, defined, and false. If you use any number other than zero, it should evaluate to true.

Note that we have used “not” keyword to negate certain Matchers. So, if an expression evaluates to “false”, we can use the “not” keyword with “toBeTruthy()” Matcher in order to assert it. The output of the preceding example should be similar to Figure 5-4.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig4_HTML.jpg
Figure 5-4

Truth Matchers in Jest

Comparison Matchers

These are the Matchers that allow you to compare the actual value to another value. The value to which you want to compare the actual value is to be passed as an input parameter to the comparison Matcher:
  • toBeGreaterThan(value) – Asserts if the actual value is greater than the provided value.

  • toBeGreaterThanOrEqual(value) – Asserts if the actual value is greater than or equal to the provided value.

  • toBeLessThan(value) – Asserts if the actual value is less than the provided value.

  • toBeLessThanOrEqual(value) – Asserts if the actual value is less than or equal to the provided value.

  • toBeCloseTo(value) – Asserts if the actual value is close to the provided value. This is specially used while dealing with floating-point values. In such cases, the precision of the expected value and the actual value might differ, so the “toBe()” Matcher (exact equality) would not work.

Let us look at an example in order to understand comparison Matchers in working. The following are the new tests added to the “functions.test.js” file:

tests/functions.test.js
...
test('comparison', () => {
  const value = 4 + 0.2;
  expect(value).toBeGreaterThan(3);
  expect(value).toBeGreaterThanOrEqual(3.5);
  expect(value).toBeLessThan(5);
  expect(value).toBeLessThanOrEqual(4.5);
  expect(value).toBeCloseTo(4.2);
});
...
The actual value in the preceding example will evaluate to “4.2”. This is compared with multiple values using the comparison Matchers. Note that in order to assert the exact value, we have used the “toBeCloseTo()” Matcher instead of “toBe()” matcher due to a likely difference in precision. The output of the preceding example should be similar to Figure 5-5.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig5_HTML.jpg
Figure 5-5

Comparison Matchers in Jest

String Matcher

This Matcher is used to compare the actual value with a regular expression:
  • toMatch(regex) – Asserts if the computed string matches the provided regular expression

Consider the following example:

tests/functions.test.js
...
test('String Matcher', () => {
  expect('Mohit is a Developer').toMatch(/Mohit/);
});
...
The preceding test asserts that the substring “Mohit” exists in the computed string. The output should be similar to Figure 5-6.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig6_HTML.jpg
Figure 5-6

String Matcher in Jest

Matcher for Iterables

This Matcher is used to check if an item exists in an iterable such as a List or an Array:
  • toContain(item) – Asserts if the computed iterable contains the provided item

Consider the following example:

tests/functions.test.js
...
const countries = [
  'India',
  'United Kingdom',
  'United States',
  'Japan',
  'Canada',
];
test('Matcher for Iterables', () => {
  expect(countries).toContain('India');
  expect(new Set(countries)).toContain('Canada');
});
...
In the preceding test, we define a list of countries and use the “toContain()” Matcher to check if “India” is present in the list. We also convert the list to a different iterable, a Set, and check if “Canada” is present in the new Set. Both the Matchers should assert true. The output should be similar to Figure 5-7.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig7_HTML.jpg
Figure 5-7

Iterable Matchers in Jest

Exception Matcher

This Matcher is used to assert if a particular exception is thrown while evaluating a particular piece of code:
  • toThrow(expectedException) – Asserts if the evaluated piece of code throws the given exception

To test this Matcher, we will go back to our “function.js” file and define a function that throws an error. We will then add a test in the “functions.test.js” file which will invoke the function and assert the exception. Consider the following example:

function.js
const functions = {
    add: (n1, n2) => n1 + n2,
    invalidOperation: () => {
      throw new Error('Operation not allowed!')
    }
}
module.exports = functions
tests/functions.test.js
functions = require('../functions.js')
...
test('Exception Matcher', () => {
  expect(functions.invalidOperation)
    .toThrow(Error);
  expect(functions.invalidOperation)
    .toThrow('Operation not allowed!');
  expect(functions.invalidOperation)
    .toThrow(/not allowed/);
});
...
In the preceding example, we have invoked the function that throws an error and matched the computed value to the expected value using the “toThrow()” matcher. Note that we can either compare it to a generic error object, a specific string that the error returns, or a regular expression. The output of the preceding example should be similar to Figure 5-8.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig8_HTML.jpg
Figure 5-8

Exception Matcher in Jest

That is it. We have covered most of the commonly used Jest Matchers. Let us now learn how to test our React components using what we have learned so far.

Testing a React Component Using Jest and Enzyme

In order to test a React component, we will have to first create a React component. Let us create a starter React application using the following command:
npx create-react-app react-jest-app

Once the starter application is created, you can delete all the files that are not necessary. I have deleted all the files from the “src” folder except “index.js” and all the files from the “public” folder except “index.html” and “favicon.ico”. I have also cleaned up the “index.html” file. The following is the code for your reference:

public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon"
          href="%PUBLIC_URL%/favicon.ico" />
    <title>React App</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
  </body>
</html>

Now that we have cleaned up our starter application, let us add our List component in the “src” folder. Consider the following code:

src/List.js
import React from 'react';
function List(props) {
  const { items } = props;
  if (!items.length) {
      return(
        <span className="empty-message">
          No items in list
        </span>;
      );
  }
  return (
    <ul className="list-items">
      {items.map(item =>
        <li key={item} className="item">{item}</li>
      )}
    </ul>
  );
}
export default List;

The preceding code is for a simple function component that fetches the items from the props and displays them as a list. Now that our component is created, we might want to instruct the “index.js” file to render it on the browser. Consider the following code for “index.js” file:

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import List from './List';
const data = ['one', 'two', 'three']
ReactDOM.render(<List items={data} />, document.getElementById('root'));
If you launch the application and visit the browser window, you should see an output similar to Figure 5-9.
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig9_HTML.jpg
Figure 5-9

List Component Using React

Now that we have created a React component, let us learn how to test it. Let us first install the Jest framework using the following command:
npm install [email protected] --save
Note

We have installed a specific version of the Jest framework. This is because applications initialized using “create-react-app” command have a dependency on this version of Jest. If your version of the Jest framework does not match the required version, you will get an error during the launch of the application mentioning the version that you need. You can resolve the error by installing the version that the application requires.

After installing the Jest framework, you will also have to add the test script in “package.json” file as per the following code:

package.json
{
  ...
  "scripts": {
    ...
    "test": "jest",
    ...
  },
  ...
}

While testing simple JavaScript functions, we used to simply invoke the functions in our tests and compare the evaluated value to the expected value using Jest Matchers. But you might wonder what to do in case of a react component because we cannot just invoke a component.

We will do exactly what React does. We will render the component on to the DOM, but it won't be the actual browser DOM; it will be a representational DOM created by a framework called Enzyme. This framework helps us simulate the runtime environment so that we can test our components. Let us install the dependencies for Enzyme framework using the following command:
npm install enzyme enzyme-adapter-react-16 –save

Note that we have also installed an adapter along with the Enzyme framework that corresponds to the version of React that we are using. We will have to configure this adapter in Enzyme before we can use the framework. To do so, we will create an “enzyme.js” file in the root directory of our application and add the following configuration code to it:

enzyme.js
import Enzyme, { configure, shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
export { shallow, mount };
export default Enzyme;

What we are doing in the preceding code is that we import “Enzyme” and “configure” from the Enzyme framework and the “Adapter” from the Enzyme Adapter. We then use the “configure()” method provided by the Enzyme framework to set the Adapter for the instance of the Enzyme that we will be using. After configuring Enzyme, we simply export it. We also import shallow and mount from the Enzyme framework and export them as they are. These are the methods that we will be using to render our React components for testing. To conclude, all the entities of the Enzyme that we need for our testing will now be imported from the file “enzyme.js” instead of directly being imported from the framework. If you try to import the modules directly from the Enzyme framework installation folder, you might run into an error because they are not configured with the Adapter.

Now that everything is configured, let us write our test for the List component. Let us write the test in “List.test.js” file in the “src” folder. Refer to the following code:

src/List.test.js
import React from "react";
import { shallow, mount } from '../enzyme';
import List from './List';
test('List Component Test', () => {
  const items = ['one', 'two', 'three'];
  const wrapperShallow = shallow(<List items={items} />);
  const wrapperFull = mount(<List items={items} />);
  console.log(wrapperFull.debug());
  expect(wrapperShallow.find('.list-items'))
    .toBeDefined();
  expect(wrapperShallow.find('.item'))
    .toHaveLength(items.length);
  expect(wrapperFull.find('.list-items'))
    .toBeDefined();
  expect(wrapperFull.find('.item'))
    .toHaveLength(items.length);
})

Note that we have used two different methods to render our component – shallow and mount. Let us understand the difference between the two. As the name suggests, the “shallow()” method limits the scope of rendering to the specified component and does not render its children components. On the other hand, the “mount()” method renders the entire tree of components. In this case, we do not have any children components so the rendering would be the same in both cases.

We pass three items to the component for rendering. The rest of the syntax is similar to the one in which we tested the JavaScript functions. The “shallow()” and “mount()methods return a React wrapper. If you want to see what the wrapper contains, you can invoke the “debug()” method on the wrapper and log the output to the console, just like we did in the preceding code. You can see in the output of the preceding test, shown in Figure 5-10, that the entire HTML rendering of our component is logged on the console. We can use the “find()” method on this wrapper to look for elements in the rendered code. We can pass multiple kinds of selectors to the “find()” method. This should be done in the “expect()” method so that we can use Matchers for the assertion. In the preceding example, we have used the class selector in order to evaluate and assert the existence and length of the list items. The following are some other selectors that you can use:
../images/488283_1_En_5_Chapter/488283_1_En_5_Fig10_HTML.jpg
Figure 5-10

Testing List Component Using Jest

ID Selector
wrapper.find('#item1')
Combination of Tag and Class
wrapper.find('div.item')
Combination of Tag and ID
wrapper.find('div#item1')
Property Selector
wrapper.find('[htmlFor="checkbox"]')
If you execute the test using the “npm test” command, you might still get an error regarding an unexpected token while rendering. This is because Enzyme does not understand the JSX code that we have supplied to the “shallow()” and “mount()” methods. We will have to install and configure a babel transformer plugin that will transform the JSX code for us. Install it using the following command:
npm install babel-plugin-transform-export-extensions --save

Also, we need to create a “.babelrc” file in the root directory of our application and provide the following configurations:

.babelrc
{
  "env": {
    "test": {
      "presets": ["@babel/preset-env",
                  "@babel/preset-react"],
      "plugins": ["transform-export-extensions"],
      "only": [
        "./∗∗/∗.js",
        "node_modules/jest-runtime"
      ]
    }
  }
}

If you run the test after installing and configuring the babel transform plugin, the test should successfully run and the output should be similar to Figure 5-10.

That is it. We have successfully tested our React component using Jest and Enzyme. With the end of this topic, we come to the end of this chapter.

Let us summarize what we have learned.

Summary

  • Jest is a test framework that can be used to test applications built using JavaScript.

  • It is good practice to suffix your test files with “.test.js”.

  • While testing, the “expect()” method is used to evaluate a JavaScript function or specify a value that needs to be tested.

  • Jest provides various Matchers that can be used on the “expect()” method to assert if the computed value matches the expected value.

  • Since React components cannot be directly invoked like functions, we will have to use the Enzyme framework that provides us the functionality to render components on a representational DOM that is created for testing.

  • The Enzyme framework provides two main methods for rendering a component – shallow() and mount().

  • We can use selectors with “find()” method to look for specific content within the rendered component.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.186.164