This article was originally published at https://www.blog.duomly.com/testing-react-app-with-jest-and-enzyme/
Intro to testing frontend applications
Writing a good quality web application requires testing, to avoid unexpected crash of one component when something changes in the other one. It’s necessary to understand the logic and plan test cases at the beginning to test the application well.
In this article, I’d like to go deeper into development concepts like Test-Driven Development (TDD), and explain to you why testing the app is essential. Besides that, I’ll go through the pros and cons of testing, and I’ll describe three types of tests used in testing web apps.
In the end, I’ll go to the practical part, where I’m going to test a simple React.js application using Jest and Enzyme step by step. The code of the application you can find on our Github.
Here’s the video tutorial, where I’m testing the application, so if you are more watching person than reading, join me on Youtube.
Let’s start!
Why are tests important?
Tests in the application are reviewing the written code if it executes correctly and brings the required results.
Testing application when we are coding is really valuable and can bring a lot of benefits for the development process, as for the future application maintenance.
The biggest benefit of a testing application is preventing regression. Thanks to tests, we can easily catch if the new code doesn’t bring old bugs again. Regression slows down the development a lot, and if there’s a way to prevent it, it should be done.
Testing the application also provides quick feedback about the code we’ve created, and instead of using multiple console.log
and manual testing, we can find out what works and what doesn’t.
Besides that, testing helps us make sure that complex components and logic work well in different situations, so there won’t be an unexpected surprise when we try to interact with certain features.
Testing helps developers to create less buggy applications and allows us to find and fix bugs early. It also simplifies adding new features and reduce the cost of building the application.
Pros and cons of testing
Even if unit testing is essential, it has some pros and cons, which developers should be aware of.
The pros of the tests are:
- possibility to find errors early and fix them early
- well written tests provide kind of documentation, which helps new developers to understand what’s going on in the application
- it reduces the time of manual testing
- helps to maintain and improve the application easily and with fewer errors
The cons of tests are:
- writing tests is time-consuming
- more code needs to be done
- badly written tests can skip important errors
What is Test-Driven Development?
Test-Driven Development is a methodology that assumes that tests are written before the code, and the task is to create the code that will pass them.
The process of coding with Test Driven Development happens with six steps flow:
- Write tests
- Run all tests, new and existing ones. In this step, new tests should fail because there’s no existing code yet.
- Write the minimum amount of code to pass the tests.
- Run the tests again to check if it passed.
- Refactor the code if necessary.
- Repeat.
TDD is a good solution for developing a good quality application, but as it has pros, it also has some cons.
Let’s see what the pros of TDD are:
- writing small tests during the development forces the code to be modular
- TDD powers good architecture and modularization
- it helps with easier maintenance
- it helps to clarify the requirements of the project from the start and helps to avoid misunderstanding
- it provides high coverage of tests in the application
The bad sides of TDD are:
- it can be difficult to write
- it can slow down the development, because of writing additional code
- it is difficult to apply to exist or legacy code tests need to be refactored sometimes
Besides a pure TDD, there’s one more solution that can be considered, especially in frontend. In the case of test-driven development, we are testing the implementation, and tests can easily fail after small changes.
But if we’d test the application's behavior, then small changes in the implementation won’t make us change the tests. This kind of approach is called Behavior Driven Development, and it’s a solution worth thinking of in case of frontend projects that will grow.
Types of tests
When testing the application, we can divide tests into three types:
Unit tests - this type of test are focused on individual components, functions, modules called units. Unit tests isolate the particular units and tests it separately to make sure each part of the application is tested and works as expected. In this kind of test, we are not testing the integration of each unit.
Component tests - this type of test are focused on testing a single component as an individual part of the application.
Snapshot tests - the snapshot tests are used to make sure that the UI isn’t changing unexpectedly. The frameworks create a snapshot from the component, and then compares it with the current state, checking for the changes.
Now, let’s go and try to test the ReactJS application with Jest and Enzyme.
How to test the ReactJS app step by step with Jest and Enzyme?
This is a practical part of this article, where I’d like to go step by step through testing my existing ReactJS application.
If you’d like to join and do it with me, you can find the code on our Github.
For the testing, I’m going to use Jest and Enzyme. Jest is a Javascript testing framework focused on simplicity. It works with most modern frontend frameworks and with pure Javascript.
Enzyme is a library for testing ReactJS components, and it’s very easy and intuitive to use.
1. Installation
Let’s start by installing all the necessary libraries. Open the application and console using yarn
or npm
to install Jest, Enzyme, and some additional plugins.
If you created your app with create-react-app
you don't have to install Jest
, it's already there.
yarn add enzyme enzyme-adapter-react-16 react-test-renderer
yarn add enzyme-to-json
If you don't have Jest
in your project yet you can install it with the following command:
yarn add jest
When it’s ready, we can open the application code and start setting up the testing environment.
2. Setting test file
Please open the setupTest.js
file, where we will need to configure the adapter to use Enzyme in the Jest environment properly.
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
When that’s ready and saved, the next step will be to think about test cases.
3. Preparing test cases
When we start testing any application, we need to have an idea of what we want to test inside it. That’s why, in this step, we are going to think of cases that will be important for us to test.
In the case of our ReactJS application, we have a very simple functionality. The component is rendered, then there’s a button that is changing the values in the boxes. When the balance box is 1000 or less, we display a notification, by changing the class.
So, first of all, let’s test is all the components are rendering.
Next, let’s check if the props passed through the components are correct.
We can then check the logic, so if the button is clicked, the value changes on both accounts.
Finally, we can test snapshots.
Now, we have four main groups of tests that we would like to test.
4. Test if components are rendering
Let’s start from the first group, where we will test if our components are rendered correctly. We are going to group it using describe
.
Let’s open the App.test.js
file where we are going to put all our tests. Because the application is not large, we are not going to put it into different files. But in case of bigger applications than two components, it’s really good to create a test file for each of them separately.
import React from 'react';
import App from './App';
import AccountBalance from './AccountBalance.jsx';
import Notification from './Notification.jsx';
import { shallow, mount } from 'enzyme';
import toJson from "enzyme-to-json";
const userBalance = {
balance: 1100,
savingBalance: 103,
}
describe("rendering components", () => {
it("renders App component without crashing", () => {
shallow(<App />);
});
it("renders App component header without crashing", () => {
const wrapper = shallow(<App />);
const header = (<h1 className="has-text-centered title is-1">Welcome in the personal finance app!</h1>);
expect(wrapper.contains(header)).toEqual(true);
});
it("renders Notification component without crashing", () => {
shallow(<Notification />);
});
it("renders button", () => {
const wrapper = mount(<AccountBalance accounts={userBalance} />);
const label = wrapper.find("#balance-button").text();
expect(label).toEqual("Send 100$");
});
});
As you can see in the code, we are first using shallow
, which is responsible for rendering the component without the children. If we need to see if there is any additional element rendered in the component we can check it by defining the element and using .contain()
method to see if it’s present.
Also, I’ve already created a userBalance
object, which is a mock for the props, which are going to be used in the next step.
5. Test passing props
Now, we can go to the next test case, which is passing props to the components. Let’s create another group with describe().
Inside the group, I’m going to set three tests, checking if the props are accepted, if they display correctly and if notification props are passed as well.
describe("passing props", () => {
const accountWrapper = mount(<AccountBalance accounts={userBalance} />);
const notificationWrapper = mount(<Notification balance={userBalance.balance} />);
it("accepts user account props", () => {
expect(accountWrapper.props().accounts).toEqual(userBalance);
});
it("contains savingBalance value", () => {
const value = accountWrapper.find(".savings").text();
const expectedValue = userBalance.savingBalance + "$";
expect(value).toEqual(expectedValue);
});
it("notification accepts props", () => {
expect(notificationWrapper.props().balance).toEqual(userBalance.balance);
});
});
So, now we can be sure that our props are passed to the child component successfully. Let’s test the logic of our application right now.
6. Test logic
The next step in our testing is to check if logic works properly. The logic is not very complicated here, because the most important functionality is changing accounts values on button click event.
In the App.test.js
let’s add another describe()
group.
describe("logic", () => {
const wrapper = mount(<AccountBalance accounts={userBalance} />);
const notificationWrapper = mount(<Notification balance={userBalance.balance} />);
wrapper.find("#balance-button").simulate("click");
it("button click - update savings", () => {
const savingsValue = wrapper.find(".savings").text();
const expectedValue = userBalance.savingBalance + 100 + '$';
expect(savingsValue).toEqual(expectedValue);
});
it("button click - update balance", () => {
const balanceValue = wrapper.find(".balance").text();
const expectedBalanceValue = userBalance.balance - 100 + '$';
expect(balanceValue).toEqual(expectedBalanceValue);
});
});
At first, I defined AccountBalance component wrapper and Notification component wrapper; then I used .simulate()
method to simulate the click event on the selected button. Next, we have three tests that are checking the functionality after a click event.
7. Snapshots
The final step of testing our simple application are snapshots. For that, we will use an additional plugin that was installed at the beginning of this tutorial, enzyme-to-json
. In this group, I’m going to define three cases as well, one for the App component, one for AccountBalance, and one for Notification component.
describe("snapshots", () => {
it("App snapshot", () => {
const tree = shallow(<App/>);
expect(toJson(tree)).toMatchSnapshot();
});
it("Accounts snapshots", () => {
const accountBalanceTree = shallow(<AccountBalance accounts={userBalance} />);
expect(toJson(accountBalanceTree)).toMatchSnapshot();
});
it("Notification snapshot", () => {
const notificationTree = shallow(<Notification />);
expect(toJson(notificationTree)).toMatchSnapshot();
});
});
In case there will be some update in the UI, and the snapshot test will fail, you can use u
to update the snapshots.
Also, after snapshot tests are done for the first time, you will see the new folder in your app called __snapshots__
where the snapshots will be saved.
8. Testing
Now, it’s time to really test our application and run the tests. Let’s open the terminal, and run the following command:
yarn test
or
npm test
Then you should see the tests are running, and you’ll see the lists of your tests, and you’ll see if they passed.
You can play with the tests to see how it looks when it fails.
Here’s my result:
Conclusion
In this article, I went through the benefits of testing web applications, and I pointed out some pros and cons of testing. Besides that, I also covered what’s Test-Driven Development and why it’s good and its disadvantages. I also went through three types of tests that are present on frontend application testing.
After that, I went to a practical task, where we have an application in ReactJS to test. I’ve installed all the necessary plugins and libraries; after that, we’ve planned the test cases and went through the tests.
I hope you’ll find this article helpful, especially if you are a beginner with testing. This tutorial will help you understand what testing is, what benefits it brings, and how to go through testing your application.
Thank you for reading,
Anna