---
title: Bitovi Academy - Testing in React
description: Write unit tests to verify components are functioning es expected.
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting?hsLang=en-us)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting?hsLang=en-us)
- ![Project management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project management](https://www.bitovi.com/services/agile-project-management-consulting?hsLang=en-us)
- ![Product design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product design](https://www.bitovi.com/services/product-design-consulting?hsLang=en-us)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services?hsLang=en-us)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting?hsLang=en-us)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers?hsLang=en-us)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting?hsLang=en-us)
- [Design Systems](https://www.bitovi.com/services/axure-figma-migration?hsLang=en-us)
- [React](https://www.bitovi.com/services/frontend/react-consulting?hsLang=en-us)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting?hsLang=en-us)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native?hsLang=en-us)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting-1?hsLang=en-us)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study?hsLang=en-us)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign?hsLang=en-us)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work?hsLang=en-us)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source?hsLang=en-us)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about perfecting software delivery
  
  ](https://bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships?hsLang=en-us)
- [![Academy](https://www.bitovi.com/hubfs/limbo/icons/icon-academy.svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy/?hsLang=en-us)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse our tools or contribute to our open source mission.
  
  ](https://www.bitovi.com/open-source?hsLang=en-us)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

[Bitovi](https://www.bitovi.com/?hsLang=en-us)

- [Services](https://www.bitovi.com/services?hsLang=en-us)
- [Client Work](https://www.bitovi.com/client-work?hsLang=en-us)
- [Our Team](https://www.bitovi.com/about?hsLang=en-us)
- [Open Source](https://www.bitovi.com/open-source?hsLang=en-us)
- [Blog](https://www.bitovi.com/blog?hsLang=en-us)
- [Academy](https://www.bitovi.com/academy/?hsLang=en-us)

[Contact Us](https://www.bitovi.com/contact?hsLang=en-us)

- [ Advanced JavaScript ](https://www.bitovi.com/academy/learn-advanced-javascript.html?hsLang=en-us)
- [ DOM and jQuery ](https://www.bitovi.com/academy/learn-dom-jquery.html?hsLang=en-us)
- [ Debug JavaScript ](https://www.bitovi.com/academy/learn-to-debug-javascript.html?hsLang=en-us)
- [ Learn TypeScript ](https://www.bitovi.com/academy/learn-typescript.html?hsLang=en-us)
- [ Learn React ](https://www.bitovi.com/academy/learn-react-vite.html?hsLang=en-us) 
    - [ Introduction to React ](https://www.bitovi.com/academy/learn-react-vite/intro-to-react.html?hsLang=en-us)
    - [ Setting Up Your Environment ](https://www.bitovi.com/academy/learn-react-vite/setting-up-your-environment.html?hsLang=en-us)
    - [ Introduction to JSX ](https://www.bitovi.com/academy/learn-react-vite/intro-to-jsx.html?hsLang=en-us)
    - [ Building Components ](https://www.bitovi.com/academy/learn-react-vite/components.html?hsLang=en-us)
    - [ Passing Props ](https://www.bitovi.com/academy/learn-react-vite/props.html?hsLang=en-us)
    - [ Routing in React ](https://www.bitovi.com/academy/learn-react-vite/routing.html?hsLang=en-us)
    - [ Styling in React ](https://www.bitovi.com/academy/learn-react-vite/styling-in-react.html?hsLang=en-us)
    - [ Managing State in React ](https://www.bitovi.com/academy/learn-react-vite/stateful-hooks.html?hsLang=en-us)
    - [ Making HTTP Requests ](https://www.bitovi.com/academy/learn-react-vite/making-http-requests.html?hsLang=en-us)
    - [ Creating Nested Routes ](https://www.bitovi.com/academy/learn-react-vite/nested-routes.html?hsLang=en-us)
    - [ Handling User Inputs and Forms ](https://www.bitovi.com/academy/learn-react-vite/controlled-vs-uncontrolled.html?hsLang=en-us)
    - [ Testing in React ](https://www.bitovi.com/academy/learn-react-vite/testing.html?hsLang=en-us)
- [ Learn Web Components ](https://www.bitovi.com/academy/learn-web-components.html?hsLang=en-us)
- [ Agile Program Management With Jira ](https://www.bitovi.com/academy/learn-agile-program-management-with-jira.html?hsLang=en-us)
- [ Learn RxJS ](https://www.bitovi.com/academy/learn-rxjs.html?hsLang=en-us)
- [ Giving a Training ](https://www.bitovi.com/academy/training.html?hsLang=en-us)
- [ Learn Technology Consulting ](https://www.bitovi.com/academy/learn-technology-consulting.html?hsLang=en-us)
- [ Learn React ](https://www.bitovi.com/academy/learn-react.html?hsLang=en-us)
- [ Learn NgRx ](https://www.bitovi.com/academy/learn-ngrx.html?hsLang=en-us)
- [ Learn Graphql ](https://www.bitovi.com/academy/learn-graphql.html?hsLang=en-us)
- [ Learn Docker ](https://www.bitovi.com/academy/learn-docker.html?hsLang=en-us)
- [ Learn CanJS ](https://www.bitovi.com/academy/learn-canjs.html?hsLang=en-us)
- [ Learn Angular ](https://www.bitovi.com/academy/learn-angular.html?hsLang=en-us)
- [ Advanced TypeScript ](https://www.bitovi.com/academy/advanced-typescript.html?hsLang=en-us)

[View More Courses](https://www.bitovi.com/academy/#courses) [ ]

# Testing in React `page`

Write unit tests to verify components are functioning es expected.

## Overview

How do you know your code is working correctly? How will you know it's still working correctly in the future after you make changes to it? Unit testing helps by verifying that given certain inputs our code generates expected outputs. So far we've copied existing tests to prove that we've completed the exercise correctly, now let's dive in and learn about how React testing is done.

## Objective 1: Render a component and verify the DOM structure

The most basic test is to render a component and validate the DOM that is generated. That's what we'll do in this first section.

### Key Concepts

- TODO

#### Introducing React testing-library

Most React unit testing is done with the [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/). The goal of this library is to test the DOM components that are generated by React rather than the React code directly. As stated in [their guiding principles](https://testing-library.com/docs/guiding-principles):

> 1. If it relates to rendering components, then it should deal with DOM nodes rather than component instances...
> 2. It should be generally useful for testing the application components in the way the user would use it.

What do we mean by "unit tests?" In the context of this lesson a unit test will involve working with a single component: passing it props and rendering it; and validating the generated DOM.

#### Rendering and verifying a component in a test

Let's take a look at some code that we added in [Handling User Inputs and Forms](https://www.bitovi.com/academy/learn-react-vite/controlled-vs-uncontrolled.html?hsLang=en-us).

```
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';
import FormTextField from './FormTextField';

describe('FormTextField component', () => {
  const mockOnChange = vi.fn();
  
  it('renders with correct label and type', () => {
    render(<FormTextField label="Test Label" type="text" value="" onChange={mockOnChange} />);
    
    expect(screen.getByLabelText(/Test Label:/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/Test Label:/i)).toHaveAttribute('type', 'text');
  });

  it('renders with the correct value', () => {
    render(<FormTextField label="Test Label" type="text" value="Test Value" onChange={mockOnChange} />);
    
    expect(screen.getByLabelText(/Test Label:/i)).toHaveValue('Test Value');
  });

  it('calls onChange prop when input changes', async () => {
    render(<FormTextField label="Test Label" type="text" value="" onChange={mockOnChange} />);
    
    await userEvent.type(screen.getByLabelText(/Test Label:/i), 'New');
    
    expect(mockOnChange).toHaveBeenCalledTimes(3);
    expect(mockOnChange).toHaveBeenCalledWith('N');
    expect(mockOnChange).toHaveBeenCalledWith('e');
    expect(mockOnChange).toHaveBeenCalledWith('w');
  });

  it('respects different input types', () => {
    render(<FormTextField label="Email" type="email" value="" onChange={mockOnChange} />);
    
    expect(screen.getByLabelText(/Email:/i)).toHaveAttribute('type', 'email');
  });
});
```

Each test consists of arguments to the Vite provided test function, `it`. The first argument is a short description of what the test is examining. The convention is that the description string takes "it" as a prefix and proceeds from there, e.g. "(it) renders with correct label and type."

The second argument is a callback function that runs the test. In the example above the first line of the callback invokes `render` and passes it a single argument, JSX for the `FormTextField` component including props. After `render` completes the `screen` (a test DOM) will contain the elements created by our React code.

```
render(<FormTextField label="Test Label" type="text" value="" onChange={mockOnChange} />);
```

Now it's time to see if the DOM matches what we expected to create. The React Testing Library provides the `screen` object that allows us to select elements from the DOM. In the current scenario we'll use `getByLabelText` which returns the `<input>` associated by `id` with a single `<label>` that has the text "Test Label". You may have intuited that `getByLabelText` accepts a string, but it also accepts a regex, in this case one that matches any part of the label text and ignores case.

```
expect(screen.getByLabelText(/Test Label:/i)).toBeInTheDocument();
```

Once `screen.getByLabelText` returns, its result can be passed to Vite's `expect` function to see if the result matches what we intended. We use `toBeInTheDocument` (`expect` was augmented with this method by importing `@testing-library/jest-dom`) to verify that the element exists in the DOM. This satisfies the first part of the test description, "renders with correct label."

If a call to `expect` does not provide the expected result an error will be thrown ending the test.

The second expect will verify the second half of the description, "and type." `expect` is passed the `<input>` element that was rendered and it is examined to see if the value of its `type` attribute is set to "text."

Note that this test could also have been written to assign the result of `getByLabelText` to a `const` then passed that const to both of the `expect` invocations.

### Setup

TODO

✏️ Create **src/components/FormSelect/FormSelect.tsx** and update it to be:

```
import { useId } from "react"

const FormSelect: React.FC<{
    children: React.ReactNode
    label: string
    onChange: (data: string) => void
    value: string
}> = ({ children, label, onChange, value }) => {
    const id = useId()

    return (
        <div className="form-group">
            <label htmlFor={id} className="control-label">
                {label}:
            </label>

            <select
                className="form-control"
                id={id}
                onChange={(event) => onChange(event.target.value)}
                value={value}
            >
                {children}
            </select>
        </div>
    )
}

export default FormSelect
```

✏️ Create **src/components/FormSelect/index.ts** and update it to be:

```
export { default } from "./FormSelect"
```

✏️ Update **src/pages/RestaurantList/RestaurantList.tsx** to be:

```
import { useState } from 'react'
import { useCities, useRestaurants, useStates } from '../../services/restaurant/hooks'
import ListItem from './ListItem'
import FormSelect from '../../components/FormSelect'

const RestaurantList: React.FC = () => {
  const [state, setState] = useState("")
  const [city, setCity] = useState("")

  const statesResponse = useStates()
  const citiesResponse = useCities(state)
  const restaurantsResponse = useRestaurants(state, city)

  const updateState = (newValue: string) => {
    setState(newValue)
    setCity("")
  }

  const updateCity = (newValue: string) => {
    setCity(newValue)
  }

  return (
    <>
      <div className="restaurants">
        <h2 className="page-header">Restaurants</h2>

        <form className="form">
          <FormSelect
            label="State"
            onChange={updateState}
            value={state}
          >
            <option key="choose_state" value="">
              {
                statesResponse.isPending
                  ? "Loading states…"
                  : statesResponse.error
                    ? statesResponse.error.message
                    : "Choose a state"
              }
            </option>
            {statesResponse.data?.map(({ short, name }) => (
              <option key={short} value={short}>
                {name}
              </option>
            ))}
          </FormSelect>

          <FormSelect
            label="City"
            onChange={updateCity}
            value={city}
          >
            <option key="choose_city" value="">
              {
                state
                  ? citiesResponse.isPending
                    ? "Loading cities…"
                    : citiesResponse.error
                      ? citiesResponse.error.message
                      : "Choose a city"
                  : "Choose a state before selecting a city"
              }
            </option>
            {state && citiesResponse.data?.map(({ name }) => (
              <option key={name} value={name}>
                {name}
              </option>
            ))}
          </FormSelect>
        </form>

        {city && restaurantsResponse.error && (
          <p aria-live="polite" className="restaurant">
            Error loading restaurants: {restaurantsResponse.error.message}
          </p>
        )}

        {city && restaurantsResponse.isPending && (
          <p aria-live="polite" className="restaurant loading">
            Loading restaurants…
          </p>
        )}

        {city && restaurantsResponse.data && (
          restaurantsResponse.data.length === 0 ? (
            !restaurantsResponse.isPending && (
              <p aria-live="polite">No restaurants found.</p>
            )
          ) : (
            restaurantsResponse.data.map(({ _id, slug, name, address, images }) => (
              <ListItem
                key={_id}
                address={address}
                name={name}
                slug={slug}
                thumbnail={images.thumbnail}
              />
            ))
          )
        )}
      </div>
    </>
  )
}

export default RestaurantList
```

### Verify

TODO

✏️ Create **src/components/FormSelect/FormSelect.test.tsx** and update it to be:

```
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import FormSelect from './FormSelect'

describe('FormSelect Component', () => {
    it('renders correctly with given props', () => {
    })

    it('calls onChange with the right value when selection changes', async () => {
    })
})

```

### Exercise

TODO

### Solution

Click to see the solution

✏️ Update **src/components/FormSelect/FormSelect.test.tsx** to be:

```
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import FormSelect from './FormSelect'

describe('FormSelect Component', () => {
    it('renders correctly with given props', () => {
        render(
            <FormSelect label="Test Label" onChange={() => { }} value="">
                <option value="option1">Option 1</option>
                <option value="option2">Option 2</option>
            </FormSelect>
        )

        // Check if label is rendered correctly
        expect(screen.getByText(/test label/i)).toBeInTheDocument()

        // Check if select options are rendered
        expect(screen.getByRole('option', { name: 'Option 1' })).toBeInTheDocument()
        expect(screen.getByRole('option', { name: 'Option 2' })).toBeInTheDocument()
    })

    it('calls onChange with the right value when selection changes', async () => {
        const handleChange = vi.fn()
        render(
            <FormSelect label="Test Label" onChange={handleChange} value="option1">
                <option value="option1">Option 1</option>
                <option value="option2">Option 2</option>
            </FormSelect>
        )

        // Simulate user changing the selection
        await userEvent.selectOptions(
            screen.getByRole('combobox'),
            'option2'
        )

        // Check if onChange was called with the right value
        expect(handleChange).toHaveBeenCalledWith('option2')
    })
})

```

## Objective 2: Simulate a user-generated event

Most components have ways to respond to events raised by user interaction, like clicking a button, how can we test code that responds to these interactions? We use another library provided by React Testing Library named [user-event](https://testing-library.com/docs/user-event/intro).

`user-event` allows you to interact with your component similarly to a user some of its methods may raise more than one event to do so, for example emitting a focus event then a click event. It also has some helpful features such as not firing a click event on an element that's hidden.

### Key Concepts

- The user-event library simulates user interactions - not events
- Tests must be marked as `async` to work with user-event

#### Concept 1

We may want to update test code to do the following: "[We recommend invoking `userEvent.setup()` before the component is rendered.](https://testing-library.com/docs/user-event/intro#writing-tests-with-userevent)"

Consider the following example:

```
import userEvent from "@testing-library/user-event";

it("toggles pickup options when clicked", async () => {
  const user = userEvent.setup();
  render(<PickupOptions />);

  expect(screen.queryByText("In-store Options")).not.toBeInTheDocument();

  await user.click(screen.getByText("Store"));
  expect(screen.getByText("In-store Options")).toBeInTheDocument();
});
```

One difference between this example and the previous one is that the callback function is now preceded by `async` because some of of the test code will `await` an action. Failing to set a test callback function as `async` or use `await` with user-event methods is a common reason why tests do not function properly or provide the results developers expect.

Before the component is rendered the `userEvent` module has its `setup` function invoked to get a `user` that can interact with the component. The `user` has a variety of functions to simulate common user actions such as clicking or typing.

After calling `render` we verify that the component has initially rendered the proper DOM structure. Since the element is not expected the `queryByText` method is appropriate to use here, this method will return null if the element doesn't exist. We use `expect` with the `not` property to confirm that the DOM does not contain the element. In most cases prefer using the testing library's API methods rather than, for example, asserting on whether or not the result of `queryByText` is null.

Now that we know the proper initial DOM was rendered let's use `user.click()` to click on an element. We pass the element to be clicked to the `click` function as its argument. Once the call to click resolves the DOM can be queried again to see the effect. Assuming the component code made the right changes the call to `getByText("In-store Options")` should return the element so it exists in the document.

### Setup

TODO

### Verify

TODO

### Exercise

TODO

###### On this page...

###### Get help

If you need [help](https://discord.gg/J7ejFsZnJ4) with an exercise, please reach out to us on [Discord](https://discord.gg/J7ejFsZnJ4)

If you find a bug, please [create an issue](https://github.com/bitovi/academy/issues/new) or email [contact@bitovi.com](mailto:contact@bitovi.com?Subject=Training%20Suggestion)

## Join the Bitovi Newsletter

 Do you want to receive updates about new Bitovi Academy courses, Bitovi open source projects, and dev community news? If so, please provide your email. We won’t spam you or give it away for any reason!  
*By providing your email you agree to receive communications from Bitovi*

![](https://bitovi.github.io/academy/static/img/close.svg)

```json
{
     "@context": "http://schema.org",
     "@type": "Organization",
     "mainEntityOfPage":{
          "@type":"WebPage",
          "@id":"https://www.bitovi.com/academy/learn-react-vite/testing.html",
          "description": "Write unit tests to verify components are functioning es expected. 
"
     },
     "url": "http://bitovi.com",
     "logo": "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
     "image": "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
     "name": "Bitovi Web App Consulting",
     "alternateName": "Bitovi",
      "naics": "541511",
      "description": "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
     "address": {
          "@type": "PostalAddress",
          "streetAddress": "1134 Pine Tree Lane ",
          "addressLocality": "Libertyville",
          "addressRegion": "IL",
          "addressCountry": "United States",
          "postalCode": "60048"
     },
     "areaServed": {
          "@type": "GeoCircle",
          "geoMidpoint": {
               "@type": "GeoCoordinates",
               "latitude": "41.8781",
               "longitude": "87.6298"
          },
          "geoRadius": "5000 km"
     },
     "telephone": "312-620-0386",
     "email": "contact@bitovi.com",
     "sameAs": [
          "https://www.facebook.com/BitoviLLC/",
          "https://twitter.com/bitovi",
          "https://www.linkedin.com/company/bitovi"
     ]
}
```