mahdiye jamali
Aug 14 · 8 min read

Testing React Applications

At GumGum, we have been migrating a legacy monolithic application to a new ReactJS application. In this article, I’m going to discuss why it was important for us to introduce tests from the very beginning of development, and how we added testing for this application.

Why do we need testing?

One of the main reasons that makes writing tests a must in each project is helping to avoid regressions by telling us if newly added code will break another part of the code. This gives developers greater confidence in adding new features by assuring that the code is behaving in a certain way. Also, with future changes, we can be more confident that the main functionality is working in the intended way when our tests pass. In other words, tests are automated quality assurance and can also result in faster releases. Another benefit is they provide insight into what the developer of the feature had in mind and what cases they considered at the time of development.

To summarize, we test a piece of code in order to verify if it is behaving in a certain way, meets all the requirements, is performant and reliable, and works as expected in the edge cases. Software testing does not guarantee that you will have no bugs, but it definitely increases the quality of the software.

Types Of Testing

Snapshot Testing: This type of testing was introduced by Facebook in the Jest library. Jest uses pretty-format to make snapshots human-readable during code review. On subsequent test runs, Jest will compare the rendered output with the previous snapshot. If they match, the test will pass. If they don’t match, either the test runner found a bug in your code that should be fixed, or the implementation has changed and the snapshot needs to be updated. [1] In snapshot testing, the goal is to make sure output of our component does not change accidentally. When we commit the components, we need to send the created or updated snapshot file as well, so we will have a point of comparison in the next changes to that component.

Isolated Or Unit Tests: Unit tests are verification if a method or a piece of code behaves in a certain way. They are written to test the code in isolation from its dependencies. In unit tests, we mock all the dependencies of the code, and we do need to separately test the behaviors that are not a concern of the component currently being tested.

Integration Tests: To the contrary of unit tests, integration tests are ways of testing different sections of the system working properly together. Integration tests are a better way of displaying that the entire system is working as expected. In integration tests, we don’t use mocks. We need to use real components. Mount() (explained in Enzyme section) is a perfect fit for integration tests, since it renders the whole underlying component tree.

End to End Tests: An end-to-end test means that you test the whole application from start to end. An end-to-end test is basically a test from the user’s perspective. For instance, after a user is logged in, making sure the user is redirected to a certain page, and in the mentioned page, if user clicks on the logout button, logging them out and redirecting them to the login page.

Testing React Applications

Here at GumGum, all of our front end projects are written in React. The fact that user interfaces depend on browsers, user interactions, and other variables makes UI testing difficult. Results are often subject to false negatives for reasons like network outage. React makes testing UI easier and more efficient since we need to test components where each of them has a specific functionality. If components are written with this in mind, they can be tested as easily as functions. If we have well-tested components, it becomes easier to refactor the code, since it guarantees the functionality of the component will not change.

Tools and Libraries We Use

In our projects we use Jest and Enzyme libraries for testing.

Jest: As mentioned before, Jest is a unit testing framework by Facebook, and there are multiple reasons that make Jest a good candidate for our tests:

  1. Needs zero configuration.
  2. Looks into the files that end in .spec, .test or the ones inside __tests__ folder to automatically find the test files.
  3. All Jest tests run through Node using a fake DOM implementation. We don’t need to start up a browser, so tests are faster.
  4. Provides great integration with Babel, which is helpful since all of our React code uses ES6 that we transpile for the browser.
  5. Parallelizes test runs to maximize performance, so our tests run faster.

To use Jest, we need to add the following packages to our package.json:

"jest"
"babel-jest" (needed so jest can transpile and understand jsx in test code)
"jest-localstorage-mock" (needed to mock where we use localStorage)
"react-test-renderer" (provides an experimental React renderer without depending on the DOM)

And add the following scripts:

"test": "jest"
"test:watch": "jest --watchAll"
"test:updateSnapshot": "jest --updateSnapshot"

Now we can run tests by simply running “yarn run test”, or run only the tests that are relevant to the changes you’ve made by “yarn run test:watch”, or update snapshot files of snapshot tests using “test:updateSnapshot”.

Jest also provides jest.fn() for mocking functions, which is useful in testing if a specific function is being called when expected, rather than the behavior of function. We will see examples of this in the next section.

Enzyme: Enzyme is a library that makes it easy to manipulate the rendered component. The API is jQuery like. You can use selectors and have access to the component’s state and props.

To use Enzyme, we need to add the following packages in package.json:

"enzyme"
"enzyme-adapter-react-15"
"enzyme-to-json"
"fetch-mock"

We also need a jestSetup.js file that provides all the methods and global values required for testing. It will look like the following:

import Enzyme, { shallow, render, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// React 16 Enzyme adapter
Enzyme.configure({ adapter: new Adapter() });
// Make Enzyme functions available in all test files without importing
global.shallow = shallow;
global.render = render;
global.mount = mount;

This file will be included in our test files by adding the following in Jest configuration in package.json:

"setupFiles": [ "<rootDir>/app/js/react/tests/jestSetup.js"]

In order to convert Enzyme wrappers to a format compatible with Jest snapshot testing, we will need to add the following line:

"snapshotSerializers": [ "enzyme-to-json/serializer"]

Enzyme provides us two ways to render React components for testing, shallow rendering and mounting.

Shallow() rendering renders the component one level deep, so you can test the component in isolation. If you change something in a child component, it won’t change shallow output of your component. If a bug is introduced to a child component, it won’t break your component’s test. It also doesn’t require DOM.

The object returned from shallow is a ShallowWrapper that has some useful properties and functions.

Ex. Snapshot testing:

it('renders properly', () => {
const wrapper = shallow(<MyComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});

In cases where the component has a dynamic property, we do not want the snapshot test to fail if the property value changes, so we can use property matchers:

expect(result).toMatchSnapshot({ id: expect.any(String), date: expect.any(Date) })

Ex. Checking that a child element has the right text:

it('has the right header text', () => {
const wrapper = shallow(<MyComponent {...props} />);
expect(wrapper.find('h1[name="header"]').first().text()).toEqual('MyComponent Header');
});

Ex. Conditional display of a child element:

const props = {};it('displays ChildComponent only if showChild is true', () => {
props.showChild = false;
const wrapper = shallow(<MyComponent {...props} />);
expect(wrapper.find('ChildComponent').first().length).toEqual(0);
props.showChild = true;
const wrapper = shallow(<MyComponent {...props} />);
expect(wrapper.find('ChildComponent').first().length).toEqual(1);
});

Mount()ing or Full DOM Rendering renders a tree into the DOM and is ideal for use cases where you have components that may interact with DOM APIs or need to test components that are wrapped in higher order components. If we need to test what is happening in lifecycle hooks, we will need to mount() the component.

Ex. Testing behavior of an element using “simulate”

it('calls methodToBeCalled when select is changed', () => {
props.methodToBeCalled = jest.fn();
const wrapper = mount(<MyComponent {...props} />);
const selectSizeElement = wrapper.find('Select[name="size"]').first();
// simulate changing the value to 50
selectSizeElement.simulate('change', { target: { name: "size", value: 50 } });
const otherExpectedParams = {};
expect(props.methodToBeCalled).toHaveBeenCalledWith({
...otherExpectedParams,
size: 50,
});
});

In the next example, “target” represents the element that initiated the event and has a value property, and “which” represents the key code of the pressed key.

it('calls onKeyPress once when keypress happens on an input element, () => {
props.onKeyPress = jest.fn();
const wrapper = mount(<MyComponent {...props} />);
const searchInput = wrapper.find('Input[name="search"]').first();
// simulate changing the value and enter
searchInput.simulate(‘keydown’, {target: {value: ‘test’}, which: 13});
expect(props.onKeyPress).toHaveBeenCalledTimes(1);
});

Adding Support for INTL

We use react-intl for internationalization of our apps, which require additional setup to test. The mount()ed and shallow()ed components will need access to the intl context.

We will need to create a intlEnzymeTestHelper.js file which creates helper functions for mount and shallow rendering and will pass intl to shallow and mount functions. Here’s how the file looks:

import { shallow, mount } from 'enzyme';
import { intlProvider, intlShape } from 'react-intl';
const intlProvider = new intlProvider({ locale: 'en' }, {});
const { intl } = intlProvider.getChildContext();
function nodeWithIntlProp(node) {
return React.cloneElement(node, { intl });
}
export function shallowWithIntl(node, { context, ...additionalOptions } = {}) {
return shallow(
nodeWithIntlProp(node),
{
context: Object.assign({}, context, {intl}),
...additionalOptions
}
);
}
export function mountWithIntl(node, { context, childContextTypes, ...additionalOptions } = {}) {
return mount(
nodeWithIntlProp(node),
{
context: Object.assign({}, context, {intl}),
childContextTypes: Object.assign({}, { intl: intlShape }, childContextTypes),
...additionalOptions
}
);
}

These functions should be imported in jestSetup.js file to be accessible from all the tests.

Adding More Integration Tests with Cypress

Going forward with adding more complex features, we realized that adding integration tests can be a little difficult to write with Jest and Enzyme. We decided to try Cypress for writing integration tests for the following reasons:

  1. The ability to run Cypress tests in a browser makes writing tests a lot easier since you can see the steps of a test and debug them visually.
  2. All the steps of a test are displayed on the Cypress browser, and allow you to time travel to a past step.
  3. Cypress has automatic waiting for commands and assertions to run before going past them.
  4. Mocking the calls to the API is simpler using routes and fixtures.

In What Ways Have Tests Helped Improve Our Applications?

In lots of cases, writing detailed unit tests right after writing a feature can help to find edge cases that have been overlooked or finding possible bugs in early stages. In general, testing has increased productivity, decreased cost of development, wasted less time for developers in finding reasons behind bugs, and in most scenarios is a great return for the investment.

Resources:

[1] https://deltice.github.io/jest/docs

[2] React Design Patterns and Best Practices

[3] https://jestjs.io/docs/en/snapshot-testing

[4] https://benmccormick.org/2016/09/19/testing-with-jest-snapshots-first-impressions/

[5] https://hackernoon.com/testing-react-components-with-jest-and-enzyme-41d592c174f

[6] https://blog.pragmatists.com/genuine-guide-to-testing-react-redux-applications-6f3265c11f63

[7] https://blog.logrocket.com/testing-react-applications-part-1-of-3-ebd8397917f3

[8] https://auth0.com/blog/testing-react-applications-with-jest/

[9] https://jestjs.io/

[10] https://docs.cypress.io


We’re always looking for new talent! View jobs.

Follow us: Facebook | Twitter | | Linkedin | Instagram

gumgum-tech

Thoughts from the GumGum tech team

mahdiye jamali

Written by

gumgum-tech

Thoughts from the GumGum tech team

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade