Testing a simple React component
Tags: react, testing
Published: 2019-08-12 19:42:20

---
title: Testing a simple React component
published: true
description: Testing a React component with local state
tags: react, testing
series: testing-react
---

In the first part of this series, we will learn how to test components with local state by testing the changes reflected in our UI.

#### Writing tests is important. And what better way to write them in the same way a user interacts with your app.

[@testing-library/react](https://github.com/testing-library/react-testing-library/) created by [**Kent C. Dodds**](https://kentcdodds.com/) is a part of the testing library project that includes test utils for React and other UI libraries.

As Kent puts it, don't test your component state. Rather, test the UI reflected because of the changes in state as that will be the way any user interacts with your app.

This is why I am creating a series, where I will add examples with specific use cases.

**The respository** with the example below is [here](https://github.com/ryands17/testing-react).

For the first example, we shall take a simple Checklist component that accepts a list of tasks to be displayed. We can toggle to check whether the task is completed or not and also view the count of the remaining tasks.

**_Note:_** I am using TypeScript for this project as it provides a great development experience, but you can create these examples in JavaScript as well by stripping of the types.

This is the task list component made with [React Hooks](https://reactjs.org/docs/hooks-intro.html). If you're not familiar with hooks, you can implement the same with class components.

```tsx
import React, { useState, useMemo } from 'react'

interface Checklist {
  id: number
  text: string
  checked: boolean
}

interface ChecklistProps {
  checklistItems: Checklist[]
}

const Checklist: React.FC<ChecklistProps> = ({ checklistItems = [] }) => {
  let [checklist, updatelist] = useState<Checklist[]>(checklistItems)

  const toggleComplete = (index: number) => {
    let newChecklist = [...checklist]
    newChecklist[index].checked = !newChecklist[index].checked
    updatelist(newChecklist)
  }

  let checkedItems = useMemo(() => checklist.filter(c => c.checked).length, [
    checklist,
  ])

  return (
    <div>
      <ul className="checklist">
        {checklist.map((checkitem, index) => (
          <li key={checkitem.id} className="list-item">
            <input
              type="checkbox"
              id={checkitem.id.toString()}
              checked={checkitem.checked}
              onChange={() => toggleComplete(index)}
            />
            <label htmlFor={checkitem.id.toString()}>{checkitem.text}</label>
          </li>
        ))}
      </ul>
      <p data-testid="checked-items">
        Checked {checkedItems} of {checklist.length} items
      </p>
    </div>
  )
}
```

Here we are displaying our tasks in an unordered list, and below that, the completed tasks count out of the total. Now the `ul` tag has a role of list so we will query the list with a specific method given to us by this library.

So the first test we would write would be to test whether our list is rendering properly. And for that we will fetch the list element and assert whether it contains the same amount of tasks that we have passed.

```tsx
import React from 'react'
import { render } from '@testing-library/react'
import Checklist from './Checklist'

const checklistItems = [
  {
    id: 1,
    text: 'Learn React Testing Library',
    checked: false,
  },
  {
    id: 2,
    text: 'Learn Advanced JS concepts',
    checked: false,
  },
]

test(`has rendered a the items passed correctly`, () => {
  const { getByRole } = render(<Checklist checklistItems={checklistItems} />)
  let ul = getByRole('list')
  expect(ul.children.length).toEqual(2)
})
```

Here we have a simple `test` block that has the required imports, our sample task list to be passed via props, and a **render** method from the testing library. This method will render our entire component and it's children, unlike the `shallow` method from [enzyme](https://airbnb.io/enzyme/) which by it's name, does a shallow rendering of the component i.e. it skips rendering the child components.

Now `render` returns us a handful of methods that help us fetch the required elements present in the component. So we're fetching the `ul` element with the `getByRole` method and the role that a `ul` has by default is `list`.

At last, we use the [matchers](https://jestjs.io/docs/en/using-matchers) that Jest provides and we can check for the length of the list-items to be equal to the tasks list that we are providing. Now, if you run this via `npm test` or `yarn test`, your test will pass!

**_Note:_** `yarn test` or `npm test` runs in watch mode by default so as you save your tests or components, it will automatically run those and you can view the output in your terminal.

Moving on, our second test would be to assert whether the checkboxes are functional and for that we need to interact with the task item, so we need to simulate a **click** event. And this library has just the method for that: `fireEvent`.

```tsx
test(`updates UI of checked item on toggling`, () => {
  const { getByLabelText } = render(
    <Checklist checklistItems={checklistItems} />
  )

  let firstItem = getByLabelText(checklistItems[0].text) as HTMLInputElement
  fireEvent.click(firstItem)
  expect(firstItem.checked).toBeTruthy()

  fireEvent.click(firstItem)
  expect(firstItem.checked).toBeFalsy()
})
```

Here we are rendering our Checklist component again in our test. And this is important, as it isolates our tests so that the previous test doesn't affect the next.

We fetch the first item in our task list using another utility method `getByLabelText` as we have used a label that will toggle our task completion, this method will find the input associated with the label.

After fetching the task, we simulate a click event on that item. Then we assert whether the `checked` property is truthy using the matchers that Jest provides. We then simulate the click event again to check whether the event is working properly and we successfully toggle the checkbox. On saving, if you check your terminal, the second test also passes!

**_Note_:** The latest version of `@testing-library/react` handles cleanup automatically after each test block so you don't need to add any cleanup logic to your tests!.

For our last test, we will verify the checked items count that we have rendered below our task list.

```tsx
test(`correctly shows the checked item length`, () => {
  const { getByTestId, getByLabelText } = render(
    <Checklist checklistItems={checklistItems} />
  )
  let p = getByTestId('checked-items')

  let firstItem = getByLabelText(checklistItems[0].text) as HTMLInputElement
  fireEvent.click(firstItem)
  expect(p.textContent).toContain('1 of 2')

  let secondItem = getByLabelText(checklistItems[1].text) as HTMLInputElement
  fireEvent.click(secondItem)
  expect(p.textContent).toContain('2 of 2')
})
```

**_Note:_** `getByTestId` works just like the `getElementById` method in JavaScript but it matches a `data-testid` attribute instead of an id.

Again we render our component in the test block, fetch the list item via the `getByLabelText` method, and we match the text of the `p` tag to contain the text that we pass using the `toContain` matcher.

Now, on saving the file, we can see in the terminal that all our tests pass! You will see that on running `yarn start` or `npm start`, our app works perfectly. We tested our component just as how we would interact with it. This was all about testing a component with local state.

Thank you for reading.