Testing React components

By

Learn how to test your first React component with Vitest or Jest and React Testing Library, using render and fireEvent to check the output and simulate clicks.

~~~

The easiest way to start with testing React components is doing snapshot testing, a testing technique that lets you test components in isolation.

If you are familiar with testing software, it’s just like unit testing you do for classes: you test each component functionality.

When I first wrote this post I assumed you created the app with create-react-app, which came with Jest already set up. The React team retired create-react-app in 2025, so today you start a React app with Vite:

npm create vite@latest my-app -- --template react
cd my-app
npm install

Vite doesn’t include a test runner. Add Vitest, which is built for Vite projects, plus React Testing Library and jsdom, the fake browser DOM the tests run in:

npm install -D vitest @testing-library/react @testing-library/dom jsdom

@testing-library/dom is a peer dependency of @testing-library/react. npm installs it for you, but pnpm and Yarn don’t, so I list it.

Then tell Vitest to use jsdom, and to expose test and expect as globals, like Jest does. Open vite.config.js and add a test key:

import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true
  }
})

Add a "test": "vitest" script to package.json and you run the tests with npm test.

One more thing about Vite: files that contain JSX must use the .jsx extension, or Vite refuses to compile them. So in a Vite project the files below are App.jsx, Button.jsx, Button.test.jsx and App.test.jsx. The code inside is the same.

Everything else in this post works the same with Jest, if you have a project that already uses it.

Let’s start with a simple test. When I first wrote this I used CodeSandbox, which ran the tests in the browser and showed the results in a Tests panel. That panel is gone from its browser sandboxes now, so run the tests locally with npm test. Create an App.js component in a components folder, and add an App.test.js file.

import React from 'react'

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  )
}

Our first test is dumb:

test('First test', () => {
  expect(true).toBeTruthy()
})

Run npm test and the runner picks up App.test.js, runs it, and reports 1 test passed.

A test file can contain multiple tests. Add a second test() call to the same file, for example one that checks expect(false).toBeFalsy(), and the runner lists both “First test” and “Second test” as passed.

Let’s do something a bit more useful now, to actually test a React component. We only have App now, which is not doing anything really useful, so let’s first set up the environment with a little application with more functionality: the counter app we built previously. If you skipped it, you can go back and read how we built it, but for easier reference I add it here again.

It’s just 2 components: App and Button. Create the App.js file:

import React, { useState } from 'react'
import Button from './Button'

const App = () => {
  const [count, setCount] = useState(0)

  const incrementCount = increment => {
    setCount(count + increment)
  }

  return (
    <div>
      <Button increment={1} onClickFunction={incrementCount} />
      <Button increment={10} onClickFunction={incrementCount} />
      <Button increment={100} onClickFunction={incrementCount} />
      <Button increment={1000} onClickFunction={incrementCount} />
      <span>{count}</span>
    </div>
  )
}

export default App

and the Button.js file:

import React from 'react'

const Button = ({ increment, onClickFunction }) => {
  const handleClick = () => {
    onClickFunction(increment)
  }
  return <button onClick={handleClick}>+{increment}</button>
}

export default Button

We are going to use React Testing Library (@testing-library/react), which is a great help as it allows us to inspect the output of every component and to apply events on them. You can read more about it on https://github.com/testing-library/react-testing-library or by watching this video.

Let’s test the Button component first.

We start by importing render and fireEvent from @testing-library/react, two helpers. The first lets us render JSX. The second lets us emit events on a component.

Create a Button.test.js and put it in the same folder as Button.js.

import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Button from './Button'

Buttons are used in the app to accept a click event and then they call a function passed to the onClickFunction prop. We add a count variable and we create a function that increments it:

let count

const incrementCount = increment => {
  count += increment
}

Now off to the actual tests. We first initialize count to 0, and we render a +1 Button component passing a 1 to increment and our incrementCount function to onClickFunction.

Then we get the content of the first child of the component, and we check it outputs +1.

We then proceed to clicking the button, and we check that the count got from 0 to 1:

test('+1 Button works', () => {
  count = 0
  const { container } = render(
    <Button increment={1} onClickFunction={incrementCount} />
  )
  const button = container.firstChild
  expect(button.textContent).toBe('+1')
  expect(count).toBe(0)
  fireEvent.click(button)
  expect(count).toBe(1)
})

Similarly we test a +100 button, this time checking the output is +100 and the button click increments the count of 100.

test('+100 Button works', () => {
  count = 0
  const { container } = render(
    <Button increment={100} onClickFunction={incrementCount} />
  )
  const button = container.firstChild
  expect(button.textContent).toBe('+100')
  expect(count).toBe(0)
  fireEvent.click(button)
  expect(count).toBe(100)
})

Let’s test the App component now. It shows 4 buttons and the result in the page. We can inspect each button and see if the result increases when we click them, clicking multiple times as well:

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

test('App works', () => {
  const { container } = render(<App />)
  console.log(container)
  const buttons = container.querySelectorAll('button')

  expect(buttons[0].textContent).toBe('+1')
  expect(buttons[1].textContent).toBe('+10')
  expect(buttons[2].textContent).toBe('+100')
  expect(buttons[3].textContent).toBe('+1000')

  const result = container.querySelector('span')
  expect(result.textContent).toBe('0')
  fireEvent.click(buttons[0])
  expect(result.textContent).toBe('1')
  fireEvent.click(buttons[1])
  expect(result.textContent).toBe('11')
  fireEvent.click(buttons[2])
  expect(result.textContent).toBe('111')
  fireEvent.click(buttons[3])
  expect(result.textContent).toBe('1111')
  fireEvent.click(buttons[2])
  expect(result.textContent).toBe('1211')
  fireEvent.click(buttons[1])
  expect(result.textContent).toBe('1221')
  fireEvent.click(buttons[0])
  expect(result.textContent).toBe('1222')
})

Check the code working on this CodeSandbox: https://codesandbox.io/s/pprl4y0wq

Tagged: React · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about react: