Testing is a very controversial concept in software development. While it is difficult for everyone to agree on the best ways to go about testing or the best tools, or even the level of priority to accrue to testing, what we can all agree on is it is a very critical aspect of any product and it should be treated as such.
In this post, we’ll take a closer look at some of the best ways you can go about testing your React applications. The concepts we will explain here will obviously apply to other JavaScript frameworks like Vue or even other languages however, for preciseness, we’ll make our demonstrations in React.
Before we get into it, it is worthy to note that this post is not a comprehensive introduction to testing. It is more of an eye-opener to the ways you should be going about testing in React (if you aren’t already).
Before we go any further, this article assumes the following:
If you are completely new to the concept of testing, think of it like this — testing is a way for you to automate the activities of your application without having to manually check if each function in a component does what it is supposed to do. Of course, this is not all there is to testing but it gives you a general idea to start.
Tests equally help with code moderation. If you have multiple contributors working on the same project, testing can help you specify the exact piece of functionalities for the individual parts of your codebase. As a result, it becomes quite easy to detect a problem in a system and proffer a fix.
To date, Jest remains arguably the most popular JavaScript framework with over 27k stars on Github. It was built by Facebook and it continues to be maintained and supported by the Jest team at Facebook. Jest is a zero-configuration javascript testing framework recommended by React and it is quite easy to use. It has a very impressive acceptance rate in 2019 by the JavaScript community with over 900 contributors.
The other popular alternatives are Mocha and Jasmine. Mocha claims to be the most used JavaScript testing framework. It has over 18k stars on Github. Asides the massive ecosystem Mocha has well-established options with a great documentation. It is also very flexible and open to a lot of extensions.
Jasmine, on the other hand, has proven to be the officially recommended testing framework for Angular.js. It has over 14k stars on Github and it is also one of the oldest testing frameworks with the most resources and community support. Even Jest was built on Jasmine.
Having considered these frameworks, it is worthy to note that there’s no explicit “best”. In the long run, it all comes down to what’s best for you. In this post, we will be using Jest for our demonstrations.
By default, create-react-app comes with these configurations. However, for flexibility and completeness, we demonstrate how to manually configure Jest with webpack for the client side.
Step 1: Run npm install --save-dev jest
on your project directory
Step 2: Head over to the package.json
file in your application and add a test script:
"script":{ "test": "jest" }
Step 3: Next, we would have to set up the .babelrc.js
file because we have a preset in the package.json
pointing to it. Jest automatically picks up the file and applies it to all of our tests
const isTest = String(process.env.NODE_ENV ) === 'test' module.export = { presets: [['env', {modules: isTest ? 'commonjs' : false}], 'react'], plugins: [ 'syntax-dynamic-import', 'transform-object-rest-spread', ], }
With this, babel can now recognize that we are passing tests then transpile all our ESmodules to CommonJS.
There are a number of ways to test React applications. We are going to look at a few of them.
Unit testing involves testing individual units/components of a software in isolation to verify its correctness. Now how do we achieve this in a React application? If we have a login component in a login.js
file like so:
function Login({ onSubmit }) { return ( <div> <Form onSubmit={e => { e.preventDefault() const { username, password } = e.target.elements onSubmit({ username: username.value, password: password.value, }) }} > <label style={{ justifySelf: 'right' }} htmlFor="username-input"> Username </label> <Input id="username-input" placeholder="Username..." name="username" style={{ flex: 1 }} /> <label style={{ justifySelf: 'right' }} id="password-input"> Password </label> <Input placeholder="Password..." type="password" name="password" aria-labelledby="password-input" /> </Form> </div> ) }
The above code is a simple login component we would be testing in a login.test.js
file.
import React from 'react' import ReactDOM from 'react-dom' import Login from '../login' test('calls onSubmit with the username and password when submitted',() => { const handleSubmit = jest.fn() const container = document.createElement('div') const form = container.querySelector('form') const {username, password} = form.element username.value = 'Kenny' passwords.value = 'pineapples' form.dispatchEvent(new window.event('submit')) expect(handleSubmit).toHaveBeenCalledTimes(1) expect(handleSubmit).toHaveBeenCalledWith({ username: username.value, password: password.value, }) ReactDOM.render(<Login onSubmit = {handleSubmit} />, container) })
The test looks for a div
and passes it into a container variable. Then from that container variable, we create a form by calling the querySelector('form')
on it.
Next, we use object destructing to get the fields from the form.element
. Because called dispatchEvent()
on the submit event, we can test for what we aspect the form to do or what value it should have when the submit event is fired. This shows that the event should be fired once and should have the username and password when fired.
form.dispatchEvent(new window.event('submit')) expect(handleSubmit).toHaveBeenCalledTimes(1) expect(handleSubmit).toHaveBeenCalledWith({ username: username.value, password: password.value, })
And of course, we can run the test with npm run test
.
Previously we were able to test a specific component to ensure that they act like they’re supposed to, but one thing we haven’t done yet is test for the structure of the user interface. We can do that with snapshot testing. Consider the example below:
render(){ <div> <p> Current count: {this.state.count}</p> <button className = 'increment' onClick ={this.increment}> + </button> <button className = 'decrement' onClick ={this.decrement}> - </button> </div> }
Imagine if a component had a specific format, like an increment button coming before a decrement button and the tests pass when this is true. If a designer changes this format, it would in-fact change the format of rendering to the DOM. So how do we avoid accidental changes to the render function of the DOM.
A snapshot test helps you take a snapshot of a component at a given time and store what it rendered previously on the DOM. So, when you run the test for the component, Jest will let you know if what you rendered is different from the snapshot it has already. You can either accept the change or be alerted to the change.
To carry out this test we will be using the react-test-renderer
form, which will give us a JSON representation of our test at a specific time. We will then store that data with Jest:
import React form 'react' import Counter from './counter' import {shallow} from 'enzyme' import renderer from 'react-test-renderer' describe('Counter component', () => { it('matches the snapshot', () => { const tree = renderer.create(< Counter/>).toJson() expect(tree).toMatchSnapshot() }) it('start with a count of 0', () => { const wrapper =shallow(<Counter/>) const text = wwrapper.find('p').text() expect(tesxt).toEqual('Current count: 0') }) it('can increment the count when the button is clicked', ()=>{ const wrapper = shallow(<Counter/>) }
First, we get a JSON representation of the counter
component that will be stored in Jest. The expect``()
method takes the tree as an argument and that is what causes the comparison with the next re-rendering.
As previously stated, integration testing is where individual units are combined and tested as a group. For example, if we had two functions working together within a single context, we would use an integration test to make sure they interact properly with each other. Let’s consider the simplest use case — add two numbers together in a component.
export const add = (x,y)=> x + y export const total = (Tax,price) => { return "$" + add(Tax, price) }
Then in the app.test.js
we do:
import {add,total} from './App' test('add', () => { expect(add(1,2)).toBe(3) }) test('total', () =>{ expect(total(5,20)).toBe($25); })
Personally, I think this is a great tool for testing React components. It addresses testing from a users perspective. It is also really helpful because it works with specific element labels and not the composition of the UI. To demonstrate how this library works, let’s refactor the previous unit test we wrote using this library.
import React from 'react' import ReactDOM from 'react-dom' import {render,simulate} from 'react-testing-library' import Login from '../login' test('calls onSubmit with the username and password when submitted',() => { const fakeuser = generate.loginForm const handleSubmit = jest.fn() const {container,getByLabelText, getByText} = render(<login onSubmit= {handleSubmit}/>) const usernameNode = getByLabelText('username') const passwordNode= getByLabelText('password') const formNode = container.querySelector('form') const submitButtonNode = getByText('submit')
In the example above, we focused more on testing elements by getting the name associated with them rather than being bothered with the UI. This is a major advantage of using this library over other alternatives like enzyme and cypress.
In this post, we have looked at various methods of testing React applications and the importance of testing. I hope this post helps you understand the importance of testing in React and shows you the ways you can go about it.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
6 Replies to "Testing React applications in 2019"
expect{handleSubmit}. What’s this syntax?
test(‘total’, () =>{
expect(total(5,20)).toBe(25);
})
I think here is a mistake.
The code in this blog is full of mistakes. It’s ironic to ask but did you test this?
This was a typo, not a new syntax. It has been updated.
Thanks for pointing this out Ander, it has been updated.
Hi Adam, yes. I’ve made the necessary updates to the Buffy snippet. You can look through again to confirm