Basics of Unit Testing In React

Kamal Walia
Nerd For Tech
Published in
7 min readSep 9, 2021

Hello everyone, I’m back with another article this time on the basics of Unit Testing In React. So without wasting any time let's start by covering the basics of Unit Testing.

Testing is the process of executing a program with the aim of finding errors.

Typically Testing is classified into three categories.

  • Functional Testing
  • Non-Functional Testing or Performance Testing
  • Maintenance (Regression and Maintenance)

Today’s topic falls in Functional Testing.

Let’s start by defining what is UNIT TESTING:

Unit Testing a type of software testing where individual units or components of the software are tested. The purpose is to validate that each unit of the software code performs as expected.

Unit Testing is done during the development (coding phase) of an application by the developers. Unit Tests isolate a section of code and verify its correctness. A unit may be an individual function, method, procedure, module, or object.

To perform Unit Testing we need to write test cases, so you might ask what is a test case.

A TEST CASE is a set of conditions or variables under which a tester will determine whether the system under test satisfies requirements or works correctly.

We have to wrap our test cases in Test Suite.

A test suite, less commonly known as a ‘validation suite’, is a collection of test cases that are intended to be used to test a software program to show that it has some specified set of behaviors.

Now it’s time we should talk about Assertions, Assertions are used for validating a test case and helps us understand if a test case has passed or failed.

Let’s now try to understand why should we use TDD(Test-driven development) to Create a React.js Component?

TDD brings many benefits to our code — one of the advantages of high test coverage is that it enables easy code refactoring while keeping your code clean and functional.

If you have created a React.js component before, you’ve realized that code can grow really fast. It fills up with lots of complex conditions caused by statements related to state changes and service calls.

Every component lacking unit tests has legacy code that becomes difficult to maintain.

How Do We Unit Test a React.js Component?

There are many strategies we can use to test a React.js component:

  • We can verify that a particular function in our props was called when certain an event is dispatched.
  • We can also get the result of the render function given the current component’s state and match it to a predefined layout.
  • We can even check if the number of the component’s children matches an expected quantity.

In order to use these strategies, we are going to use two tools that come in handy to work with tests in React.js: Jest and Enzyme.

Using Jest to Create Unit Tests

Jest is an open-source test framework created by Facebook that has great integration with React.js. It includes a command-line tool for test execution similar to what Jasmine and Mocha offer.

It also allows us to create mock functions with almost zero configuration and provides a really nice set of matchers that makes assertions easier to read.

Furthermore, it offers a really nice feature called “snapshot testing,” which helps us check and verify the component rendering result.

Now Let’s start writing some code to understand how we can test our components.

Using Assert

var assert = require(‘assert’);describe(‘Basic Mocha String Test’, function (){it(‘should return number of characters in a
string’, function () {
assert.equal(“Hello”.length, 4);
});
it(‘should return first character of the string’,function () {
assert.equal(“Hello”.charAt(0),‘H’);
});
});

Let’s try to understand what the above code is trying to do:-

Assert helps to determine the status of the test.
Describe is a function that holds the collection of tests. It takes two parameters, the first one is the meaningful name to functionality under test and the second one is the function that contains one or multiple tests. We can have nested describe as well.
IT is again a function that is actually a test itself and takes two parameters, the first parameter is the name of the test, and the second parameter is a function that holds the body of the test.

There are times when we are using a 3rd party library in our component and when we try to run our test case, it doesn't work because our testing library doesn’t know about that library. For scenarios like this, we can create mock functions using jest.

Mock functions are also known as “spies”, because they let you spy on the behaviour of a function that is called indirectly by some other code, rather than only testing the output.

Let’s imagine we’re testing an implementation of a function forEach, which invokes a callback for each item in a supplied array.

function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}

To test this function, we can use a mock function, and inspect the mock’s state to ensure the callback is invoked as expected.

const mockCallback = jest.fn(x => 42 + x);forEach([0, 1], mockCallback);// The mock function is called twiceexpect(mockCallback.mock.calls.length).toBe(2);// The first argument of the first call to the function was 0expect(mockCallback.mock.calls[0][0]).toBe(0);// The first argument of the second call to the function was 1expect(mockCallback.mock.calls[1][0]).toBe(1);// The return value of the first call to the function was 42expect(mockCallback.mock.results[0].value).toBe(42);

.mock property:- All mock functions have this special .mock property, which is where data about how the function has been called and what the function returned is kept.

As I said that we can also mock 3rd party modules so here is an example of that as well:-

jest.mock('axios');test('should fetch users', () => {const users = [{name: 'Bob'}];const response = {data: users};axios.get.mockResolvedValue(response);// or you could use the following depending on your use case:// axios.get.mockImplementation(() => Promise.resolve(response))return Users.all().then(data => expect(data).toEqual(users));});

Using Enzyme to Mount React.js Components

Enzyme provides a mechanism to mount and traverse React.js component trees. This will help us get access to its own properties and state as well as its children’s props in order to run our assertions.

Enzyme offers two basic functions for component mounting: shallow and mount. The shallow function loads in memory only the root component whereas mount loads the full DOM tree.

The enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components’ output. mount(<Component />) for Full DOM rendering is ideal for use cases where you have components that may interact with DOM APIs or may require the full lifecycle in order to fully test the
component (ie, componentDidMount etc.)

Mount is the only way to test componentDidMount and componentDidUpdate. Full rendering including child components. Requires a DOM (JSdom).

shallow(<Component />) for Shallow rendering is useful to constrain yourself to testing a component as a unit, and to ensure that your tests aren’t indirectly asserting on behaviour of child components.

Simple shallow

Calls:

  • constructor
  • render

Shallow + setProps

Calls:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render

Shallow + unmount

Calls:

  • componentWillUnmount

Examples of Shallow:-

Basic Example

it('should render three <Foo /> components', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find(Foo)).to.have.length(3);
});

Render Children when passed a child component

it('should render children when passed in', () => {
const wrapper = shallow(
<MyComponent>
<div className="unique" />
</MyComponent>
);
expect(wrapper.contains(<div className="unique" />)).to.be.true;
});

Stimulating a Click:-

it('simulates click events', () => {
const onButtonClick = sinon.spy();
const wrapper = shallow(
<Foo onButtonClick={onButtonClick} />
);
wrapper.find('button').simulate('click');
expect(onButtonClick.calledOnce).to.be.true;
});

Now let’s talk about SPY, A test spy is a function that records arguments, return value, the value of this , and exception thrown (if any) for all its calls. There are two types of spies: Some are anonymous functions, while others wrap methods that already exist in the system under test.

sinon.spy(object, “method”) creates a spy that wraps the existing function.

Examples

We can check how many times a function was called

it('should call save once', function() {   
let save = sinon.spy(Database, 'save');
setupNewUser({ name: 'test' }, function() { });
save.restore();
sinon.assert.calledOnce(save);
});

We can check what arguments were passed to a function

it('should pass object with correct values to save', function() {    
let save = sinon.spy(Database, 'save');
let info = { name: 'test' };
let expectedUser = {name:info.name, nameLowercase: info.name.toLowerCase()};
setupNewUser(info, function() { });
save.restore();
sinon.assert.calledWith(save, expectedUser);
});

Lastly, I want to cover stubs also, Stubs are like spies, but they replace the target function. You can use stubs to control a method’s behavior to force a code path (like throwing errors) or to prevent calls to external resources
(like HTTP APIs).

Stubs have a few common uses:

  • You can use them to replace problematic pieces of code
  • You can use them to trigger code paths that wouldn’t otherwise trigger — such as error handling
  • You can use them to help test asynchronous code more easily

Examples:-

Our earlier example uses Database.save which could prove to be a problem if we don’t set up the database before running our tests. Therefore, it might be a good idea to use a stub on it, instead of a spy.

it('should pass object with correct values to save', function() {
let save = sinon.stub(Database, 'save');
let info = { name: 'test' };
let expectedUser = {name: info.name, nameLowercase: info.name.toLowerCase()};
setupNewUser(info, function() { });
save.restore();
sinon.assert.calledWith(save, expectedUser);
});

Stubs can also be used to trigger different code paths.

it('should pass the error into the callback if save fails', function() {
let expectedError = new Error('oops');
let save = sinon.stub(Database, 'save');
save.throws(expectedError);
let callback = sinon.spy();
setupNewUser({ name: 'foo' }, callback);
save.restore();
sinon.assert.calledWith(callback, expectedError);
});

Hope you liked my article thanks.

--

--

Kamal Walia
Nerd For Tech

👨‍💻 Software Engineer | Tech Enthusiast | Wordsmith 📝