Introduction
Let's face it -- There is no better way to test your applications better than putting them to, well, the test! We can build all day long and visually get the results we are looking for. But, what happens when there is a hidden bug? What if this bug reveals a pretty serious vulnerability? All of this can be avoided in our React apps using unit testing.
Necessary Dependencies
Okay, so just what ARE the needed dependencies and how do we install them? Don't worry -- I'm including this valuable information right here and now. We are going to need a total of three dependencies, so let's start by installing those first:
npm install jest-dom
npm install react-test-renderer
npm install @testing-library/react
Even if you have any of these dependencies, it is ALWAYS a good idea to make sure your versioning is up-to-date. If your application was created using create-react-app
, you are likely already set up with @testing-library/react
since it is used for testing your default App.test.js
file which comes with the initial setup.
How To Do Your Own Testing
I don't wanna be the guy to put you to work, but it's very important for you to follow along in code as you read. Unit testing isn't difficult, but it can be a little confusing and overwhelming if you try to understand it based on reading alone.
Okay, so let's get right down to it!
Application Setup (for testing)
Start by creating a new folder under src
called components
. Within this folder, create another folder named buttons
. From here, create two files in your buttons
folder. They are button.js
and button.css
.
Inside of button.js
, place the following code:
// /src/components/buttons/button.js
import React from 'react';
import './button.css';
function Button({label}){
return <div data-testid="button" className="button-style">{label}</div>
}
export default Button;
Here, we are using a functional component that is taking {label}
as a prop. You'll also notice we are using data-testid="button"
. data-*
is an HTML attribute we can use for testing, and this is particularly useful for when another dev comes along and changes the name of your IDs or classes. You can look up data
for more information but for those on limited time, this is a great source that sums up the concept.
Okay, so let's visit roughly the top level of our application (App.js
). Apply this code:
// App.js
import React from 'react';
import Button from './components/buttons/button';
function App() {
return (
<div className="App">
<header>
<Button label="click me please"></Button>
</header>
</div>
);
}
export default App;
The div
with the class "App" isn't important, but at this point, you should delete App.test.js
as editing App.js
will beak the testing later. We do not need App.test.js
for this tutorial.
Next, we will head back to our buttons
folder and open button.css
. Place in the following code:
// /src/components/buttons/button.css
.button-style {
border: 1px solid grey;
padding: 10px;
text-align: center;
}
This part about adding CSS styling isn't really necessary unless you plan on starting the application to get a visual of your rendered functional component. It was only included in the tutorial for fun! :)
Unit Testing Time
Finally, the fun part! In your src/components/buttons
folder, create a new folder named __test__
. Inside of this folder, we are going to create a file named button.test.js
. When your unit testing begins, it will travel down the tree of your application looking for any files with .test.js
as the extension. This information will be important and further explained shortly.
Inside of button.test.js
, we want to start with some basic imports at the top of our file. It should look like this:
// /src/components/buttons/__test__/button.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import Button from './../button';
import { render } from '@testing-library/react';
Please check React docs and Google if you are not familiar with imports within your application tree. If you're developing in React, you should already know how to use them.
Alright, so we've taken { render }
from @testing-library/react
. We are immediately going to use this in our first test below our imports.
// uses @testing-library/react
it('renders without crashing', () => {
const div = document.createElement("div");
ReactDOM.render(<Button></Button>, div)
})
it()
takes two arguments. We are giving the test a description string for the first argument to "renders without crashing", and then an anonymous function for the second argument which will be responsible for returning a boolean if the function executes without issue. To put it in English, we are setting a variable div
assigned to document.createElement("div")
. Then, we are rendering our Button component to the DOM.
To run this first test, go ahead and type npm test
in your IDE terminal and press Enter when prompted. Go ahead, I'll wait. :)
...
Your first test has passed! We have validated that an element can be rendered without crashing the application. Well done! To close out your testing, just press CTRL + C
in your IDE terminal. Now we can move on to unit testing for the present values in our DOM elements.
We are going to need another import. Go ahead and add this to your imports at the top:
// /src/components/buttons/__test__/button.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import Button from './../button';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
// New import ^
In @testing-library/jest-dom/extend-expect
, we are gaining access to the expect()
function that comes with the jest-dom
dependency.
Next, add this test below your first one:
//uses @testing0library/jest-dom/extend-expect
it('renders button correctly', () => {
const { getByTestId } = render(<Button label="click me please"></Button>)
expect(getByTestId('button')).toHaveTextContent("click me please")
})
This is where we are putting our Button
component to the test! We are creating a button under the variable getByTestId
and a label of "click me please" and expecting it to contain "click me please". This test would fail if we could not pass our props down into our component. However! Go ahead and run your tests again. Surprise! They passed again!
We have a problem though... Take a look at these two tests below:
//uses @testing0library/jest-dom/extend-expect
it('renders button correctly', () => {
const { getByTestId } = render(<Button label="click me please"></Button>)
expect(getByTestId('button')).toHaveTextContent("click me please")
})
it('renders button correctly', () => {
const { getByTestId } = render(<Button label="save"></Button>)
expect(getByTestId('button')).toHaveTextContent("save")
})
Notice anything strange? They are both the same test, but with different prop values being tested between them.
if we try to run our tests, we get an error! found multiple elements by [data-testid="button"]
. What we need to do is include some way to clean up our tests when we're done with each one. Luckily, this is very easy and simple to do.
Remember our import friend at the top import { render } from '@testing-library/react';
? We're going to make one small change:
import { render, cleanup } from '@testing-library/react';
.
Then, right below your imports and above your tests, include this line:
afterEach(cleanup)
Now you can run your tests again. Check it out, they're passing again!
For one final lesson, we're going to be introduced to JSON snapshots of our tests. These are useful as snapshots create an instance of our passing tests and compares that snapshot to future tests to make sure they match.
Start with adding our final import at the top of our file:
import renderer from 'react-test-renderer';
Now that we have renderer
, we're going to write our final test. Place this last test at the bottom of your other tests:
// uses renderer to create a snapshot of the Button component
it('matches snapshot', () => {
// creates snapshot and converts to JSON
const tree = renderer.create(<Button label="save"></Button>).toJSON()
// expects the snapshot to match the saved snapshot code found in the __snapshot__ folder
expect(tree).toMatchSnapshot()
})
As mentioned above, we have created a snapshot of our tests that will be used to compare to other test runs. You can find this new snapshot under /src/components/buttons/__test__/__snapshots__/button.test.js.snap
. This new folder/file is created for you after you run your tests.
Conclusion
There you have it! These are the basics in React Unit Testing. Once you've got these fundamental concepts down, you can continue to explore more complex testing. Additionally, you can use tests to create labs for anyone you end up mentoring in your longterm career.
If you'd like access to the repo for this tutorial, you can find it here
Happy coding!
Top comments (0)