Pagepro Blog

JavaScript

Effective Unit Testing of React Components (Part 1)

Posted on .

Effective Unit Testing of React Components (Part 1)

Introduction

User interface built with React is a sum of components properly working together. Most of the components are affected by props passed by parent and higher order components, internal state modifications, event handlers, conditional rendering. If you want to be confident that your application will do the job in the real world, it’s good to start checking all of these factors in isolation. That’s what unit testing is all about.

Yet, there are a lot to preliminary steps before starting your testing journey: planning how to structure your tests, choosing assertions matchers, picking a tool to display results and coverage reports and providing browser environment replacement.

To succeed we have to get it all right. In the same time, we want to avoid configuration madness, so choosing our technology wisely and plan test cases ahead is a must.

This article is divided into three parts. I will start with the description of the tools that I recommend for unit testing and present to you a contract-based approach for writing component assertions.

In the next part, we will practice the process of writing the component contract.

Finally, we will turn acquired knowledge into the actual unit tests code.

Our technology stack

Jest

We will pick Jest as our testing framework. It’s marketed as “out of the box” solution, shipped with the most popular boilerplate in React’s ecosystem: create-react-app. If you are bootstrapping your app with this project, most of the work will be already done for you.

If you are not using CRA, don’t worry, you can head to the short and easy to follow guide that describes configuration process.

Jest provides most of the functionalities that we wanted from our testing environment: structure, matchers, displaying results and coverage reports, mocks etc.

If you want to dig deeper into capabilities of testing React with Jest, check out the dedicated documentation chapter.

Enzyme

Enzyme is the second tool in our arsenal. Made by Airbnb with unit testing of React components in mind. It makes a rendering, analyzing and asserting components easy.

Enzyme will render your component and let you analyze what actually got rendered via intuitive API. It uses the same library as jQuery Core (cheeriojs) for selectors, so if you used this legendary front-end lib, you will find Enzyme quite familiar even if you never used it before.

Enzyme let us render components in three different ways:

  • Shallow – it renders only given component, without nested children
  • Full – it renders component and all of his descendants
  • Static – it renders static HTML code that is available in the browser

Shallow and static rendering doesn’t need DOM, so this kind of rendering takes a lot of less time than full rendering that actually needs some browser implementation (jsdom is the most popular one) to run.

There are mixed opinions about which rendering technique to go with for the best balance between fast execution and high reliability.

I go with shallow rendering for Unit Testing and leave full rendering for integration tests.

This library has a rich, powerful API – I will show you 20% that give 80% of the results. You can check the rest of functionalities in the library docs.

To use Enzyme with Jest, we will need two packages: enzyme and enzyme-adapter-react-X (replace X with the version of React used in your project, ie. 16). Enzyme needs the adapter to provide compatibility with the given version of React API.

We have to create the first configuration file (and by the way, the last one, how cool is this?): src/setupTests.js. I recommend to stick with this file naming and path, it is automatically recognized by Jest.

Let’s start with basic Enzyme configuration.


import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

We want to reduce the number of redundant imports in our test files so we will assign rendering methods as properties of node.js’s global object. This way we will be able to access them in all of our test.js files.


import Enzyme, { shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });

global.shallow = shallow;
global.mount = mount;
global.render = render;

Let’s get formal

Defining a clear component contract makes unit testing a lot easier.

The component’s contract is nothing else but our expectations about the features provided by the component and assumptions about how it will be used in the application.

Every time you create a new component you probably have its contract loosely defined in your mind.

I found that writing component contract on paper is really useful. It improves component’s design by exposing possible inconsistencies and, what is really important from today’s article point of view, leads to the minimal amount of unit testing code.

We want our tests to be really useful and check those parts of the component that actually matter. We don’t want to go into components internals that are prone to frequent changes.

There is some kind of symbiosis between component contract and it’s unit testing. Contract points us to what should be tested, tests help us to define contract more precisely. This process is all about this feedback loop, so don’t get stuck on any side of this equation.

How to create a valuable contract?

When we prepare the first draft of the component contract, there are some important factors that should be taken into consideration, no matter the given component responsibility:
props that component will receive (via the parent, hoc, redux store subscription, context etc)
state and how it will be mutated via handling user interactions lifecycle methods.

There is a lot to think about, but I found that asking those simple questions helps in pinning down the most important pieces of information:

  • What component renders?
  • What component will share with its children via props?
  • How component (and its children) interacts with the user?
  • What is actually going on in lifecycle methods?

Those four simple questions will help you steer your attention toward the most important part of every component.

If you will find yourself stuck during answering one of those questions, you have a pretty clear indication that your component is poorly designed. Probably it tries to do many things at once. Try to split it into smaller components that will be easier to test thanks to more precisely defined responsibility.

Summary

We have our testing environment well prepared. Jest and Enzyme are all we need to approach unit testing of our components in an effective manner.

You have also learned about the component contract and its utility in the process of defining the scope of unit tests.

In the second part of this series, I will show you how defining the component contract looks in practice.

Marcin Czarkowski

Marcin Czarkowski

There are no comments.

View Comments (0) ...
Navigation