DEV Community

loading...
Cover image for Writing Your First React Test

Writing Your First React Test

Hal Dunn
a softie and a software engineer
・6 min read

Cover image unrelated -- nice to look at though, right? Discovery Park, Seattle, WA.

This post will assume that the reader has a good understanding of the React basics. It will also involve some coding, which you are welcome to code along with. The repository for the starter code can be found HERE. To view the finished product with tests, use the same repository, but switch to the with-tests branch.

Before we make any changes, take a moment to poke around the code. It's fairly simple -- just two components & a little bit of state to swap the image between a dog and not-a-dog.

Since this repository was created with create-react-app, there are some testing tools which are already in the code that we don't have to build or add ourselves.

Screenshot of file tree

Firstly, there is a file called setupTests.js, which contains the basic import for the Jest test runner. This file can be edited for fancier, more complicated tests in the future, but we won't need to do much with it right now.
Secondly, you'll see a file called App.test.js. Now, I know this is crazy, but that's where we'll write the tests for the App component. The file extension matters, as when we input the command to run the tests (either npm test or yarn test depending on your package manager), anything with a .test.js file extension will get read & executed.
There is also some code which already lives inside of the App.test.js file, which we can use to make some basic assumptions about how tests look. We might as well check it out.

import { render, screen } from '@testing-library/react'
import App from './App'

test('renders learn react link', () => {
  render(<App />)
  const linkElement = screen.getByText(/learn react/i)
  expect(linkElement).toBeInTheDocument()

})
Enter fullscreen mode Exit fullscreen mode

Let's break down what we are looking at before changing it.

I think the most confusing and, potentially most important part to recognize with this small snippet is that there are two different testing packages being used. The first package we are using is the React Testing Library. It's the more obvious package, because the import is right at the top, like normal. We are importing render, which will allow us to access an instance of any component, and screen, which will allow us to make queries off the DOM similarly to vanilla JavaScript, after said component is rendered.

The second package is Jest, which is a "test runner". Jest ships out of the box when you make a project using create-react-app, but it is a third party library. You don't have to use Jest if you discover that an alternate test runner offers more applicable functionality, but it's the most widely used and a great place to start.

What's a test runner?

Test runners are not all the same, but their overall purpose is to read the test files and print some output based on whether or not the expectations, otherwise known as assertions, are met for each test.

Jest itself is a runner (meaning you can read tests with it), assertion library (meaning you can write expectations with it), and a mocker (meaning you can create a fake replica of outside functionality to mess around with in the testing space). Now let's look at another picture of landscape really quick.

Discovery Park, WA

Discovery Park again!! What a nice damn place to be.

Okay, back to the code snippet:

import { render, screen } from '@testing-library/react'
import App from './App'

test('renders learn react link', () => {
  render(<App />)
  const linkElement = screen.getByText(/learn react/i)
  expect(linkElement).toBeInTheDocument()

})
Enter fullscreen mode Exit fullscreen mode

We can now determine which parts are Jest: test and expect (assertions)

and which parts are the React Testing Library: render and screen.

One last thing you may be wondering... why don't we have to import test and expect from Jest?

And that the answer to that would be... that Jest sneakily adds its methods into the global scope of your .test.js files. If you pop a console.log(global) into one of your test files and then run it, you can physically see every single method available to you in that space. Be warned, that is a huge console.log you are about to see. But expect and test are in there.

At this point, go ahead and run yarn test if you haven't already. Our app doesn't have the learn react link, so of course the original test won't pass.

Determining What to Test

Now that we've got a taste of what tools create-react-app grants us, we can begin to think about what tests to write. There are three main types of tests:

  1. Unit - Tests one single piece of functionailty, like a method or a piece of state, in a vaccuum.
  2. Integrations - Tests a group of methods or components together, to make sure they work properly in combination.
  3. End-to-End - Begins where the site user would begin and tests the entirety of the available app.

⚠ Which type of test do you think is currently being rendered in App.test.js?

I've read differing opinions on which style you should begin your testing with, but the one that makes the most sense to me is to write integrations tests. Overall, the prevailing philosophy is to test your app the way that it might be used by a user. So let's take that and look at what our app does. Go ahead and spin up the app from a second terminal using yarn start.

Screenshot of the pre-built app

Wow, it's a dog.

While running the app, you should be able to click on the image to flip it back and forth between a dog, and not-a-dog. So there you have it: seems like we should write a test to make sure that clicking on the image switches it back & forth. After all, that's what our users are doing.

Writing the Test

We'll start by rendering the app. That part we don't have to change.

import { render } from '@testing-library/react'
import App from './App'

test('Switches image upon clicking', () => {
  render(<App />)

})
Enter fullscreen mode Exit fullscreen mode

Next, we need to grab the image from the DOM, so we can simulate a click. The screen import from React Testing Library is pre-bound to the document.body, so conceptually, you can query screen like you would the document.body in vanilla JS. The methods are a little different, but the idea is the same. If you are using VS Code, there is a quick way to check out all the methods available on the screen object. Head over to your App.test.js file and type screen., and you should see some options pop up in a drop down that you can scroll through with the arrow keys.

You may notice that "getByTagName" is not available -- so how do we grab an image? Well, images have alt texts.

import { render, screen } from '@testing-library/react'
import App from './App'

test('Switches image upon clicking', () => {
  render(<App />)
  const img = screen.getByAltText(/My dog, Beany/i)
  console.log(img)

})
Enter fullscreen mode Exit fullscreen mode

Since this is our first time poking around with tests, I recommend console.logging as much as you need to in order to prove that your ideas are working. If you run the above code, you should see a log in the test server that looks like a React Node.

Now we've rendered the App and we have ahold of the image, so it's time to simulate a click.

import { render, screen, fireEvent } from '@testing-library/react'
import App from './App'

test('Switches image upon clicking', () => {
  render(<App />)
  const img = screen.getByAltText(/My dog, Beany/i)
  fireEvent.click(img)

})
Enter fullscreen mode Exit fullscreen mode

We can import the fireEvent object from the React Testing Library and use its click method. By passing it the image, we would expect that the image on the screen is now changed. So for the final part of the test, we're going to write out that expectation.

import { render, screen, fireEvent } from '@testing-library/react'
import App from './App'

test('Switches image upon clicking', () => {
  render(<App />)
  const dogImg = screen.getByAltText(/My dog, Beany/i)
  fireEvent.click(dogImg)

  const notDogImg = screen.getByAltText(/Rainbow frowny face/i)
  expect(notDogImg).toBeInTheDocument()
})
Enter fullscreen mode Exit fullscreen mode

And there you have it: you have written your first test in React. Hopefully you have also gained some tools for understanding the testing libraries and the ability to write more!

Might as well sign off with a nice picture of some landscape.

Double Bluff Beach on Whidbey Island

Another one of beautiful, sunny Discov- just kidding! This one is from Whidbey Island.

Happy coding!

Discussion (3)

Collapse
stevetaylor profile image
Steve Taylor

Testing in a fake browser environment is a fool’s errand. Use Jest for unit tests that are actually unit tests. Otherwise, use Mocha in the browser or Cypress. You can’t efficiently diagnose test failures in JSDom and it doesn’t give you the level of assurance you’d get from testing in a real browser.

Collapse
halented profile image
Hal Dunn Author

Thanks for the input! Because Jest comes with React, I assumed it was the test framework most widely used. Do you think taking the time to write unit tests in Jest is worth it if you move integration tests to Mocha, or would you write all tests in one framework?

Collapse
stevetaylor profile image
Steve Taylor

Having used both Jest and Mocha extensively, I’d move all front-end tests to Mocha and run them in a browser. Having two different test runners is a hassle.