Skip to Content
Test React UseState with Jest

Test React UseState with Jest

Hey, hello, hi thanks for stopping by! Want to learn how to test React UseState with Jest? Well, look no further. This article covers basic component testing, as well as testing for state. We’ll be using a React Typescript project, but don’t worry if Typescript is new to you, this informative article will help fill in the gaps.

Estimated reading time: 8 minutes

If you’ve been to this site before, then you know I love a button. Let’s face it what component could be more simple to understand than a button? So, in my usual fashion, we’re going to create a Button Component.

The Button Component

Below is the full code of our Button component.

import React, { useState } from 'react'

interface IButton {
label: string
icon: string
}
const Button: React.FC<IButton> = ({label, icon}) => {
    const [state, setState] = useState(false)

    return (
        <>
        <span>{icon}</span>
        <button disabled={state} onClick={() => setState(true)}>{label}</button>
        </>
    )
}
export default Button

If you like building buttons, check out this how to create accessible toggle buttons with Material UI. If building stuff from scratch is more your thing, check out this tutorial on making accessible toggle buttons.

The Button Type Interface

The following code is how we declare data types for the props in a React component. It’s called an interface and it just simply helps us test, and work with our Button component to cut down on errors and confusion.

interface IButton {
label: string
icon: string
}

This interface is saying, hey the label prop and the icon prop are both strings. Now, whenever I use this Button component, if I’m using VSCode, my IDE will yell at me to remind me to add in the label prop and icon prop. It will also tell me that they must be strings.

the standard App component from create-react-app shows import statements for react, logo, App.css and also Button. The App functional component has the Button component inside of it but under it is a red squiggly line

As you can see in the above image, I’ve imported the Button component to use in my React App. It has a red squiggly line. When I mouse over the Button component watch the magic happen:

when the use clicks the button component in the app component a modal pops up and says type {} is missing follwoing properties from IButton: label, icon there is a view problem and quick fix link on the bottom of the modal

We see this modal appear that tells us exactly what information we’re missing. Next I click on the ‘Quick Fix’ then Add missing attributes and voila. We see the label and icon props have been added as empty strings.

now inside the button component there is a label attribute and icon attribute each with an empty string inside curly braces

Now I know I need to enter in a label and icon prop and they must be strings. I’m smarter than VSCode (sort of) so I know I can remove the curly braces and replace them with double quotes like this:

<Button label="" icon="" />

Want to learn more about Typescript? Check out this post about building with WordPress and Typescript.

The Button Props in our Jest test

The great news is that when we render our Button component inside of our Jest test, we’ll get this same help from Typescript when testing our props.

Let’s take the code below in isolation to see what I mean:

    it('tests the button props', () => {
        render(<Button />)
    })

In VSCode, we get the same red squiggly line under the button component. In our render function in this test, we can add the label and icon and they can be any string we want. In a jest test all we want is to know that our component will behave as expected. Therefore, we mock the prop values to be any string of our choosing.

 it('tests the button props', () => {
        render(<Button label="poopy" icon="icons" />)
    })

As you can see the value in and of itself doesn’t matter. What matters is that we have props and they are strings. Now we can use these mock values to test what we expect our component to behave like.

it('tests the button props', () => {
        render(<Button label="poopy" icon="icons" />)
        expect(screen.getByRole('button', {name: 'poopy'})).toBeInTheDocument()
    })

In the code above, I am grabbing an HTML element with an aria role of button. The button text should say “poopy” because that is the label i’ve given it and if you go back to the Button component you’ll see the label prop is implemented as the button text. This test will pass.

Rewind: Set up our Jest Unit test

Let’s go backwards a bit and start at the beginning. Let’s create our unit test file. In the same directory as our Button Component, we’ll create a corresponding unit test file. My Button component file name is Button.tsx, so my Button jest test will be Button.unit.test.tsx

Below is what the file structure looks like:

inside the src directory there is a components folder. inside of that is a Button.tsx file and a Button.unit.test.tsx file.

Inside the Button.unit.test.tsx file let’s scaffold our test and create a test suite.

Import modules into the Unit test

Starting with our imports, these three imports will pull in all of the code we’ll need to create a unit test.

import React, { useState as useStateMock } from 'react';

The React and useState imports from React will allow us to use react and useState within our component test.

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

The render and screen modules are utilities we use to run our tests.

import Button from './Button';

Finally, we import the Button component which we need so we can test it.

Create a Test Suite

When testing a React component, we should create a test suite by first adding the describe() function and passing the name or our component as the first parameter. This will prove useful when we are examining our tests that have run as it organizes our test cases.

Below our imports we’ll add a describe block like so:

describe('Button', () => {
})

Next, inside of our describe block we’ll add an it() block. The it() block is where each individual test will be housed. What we are testing should be the first parameter of the it block like this:

describe('Button', () => {
    it('tests the button props', () => {
       // add test code here
    })
})

When I run npm test in my terminal and my jest test fails, I’ll now be able to see the component and the test that failed like so:

Fail in red outline from a failed jest test. the failing test says Button > test the button props

We covered in a previous section how to test button props, so let’s move onto the meat of this post and test the state of our Button component.

Testing State in React

To use state in a jest test, we’ll need to actually use useState from React. The jest.requireActual() function allows us to return the actual React module instead of mocking it out.

At the top of our file under our import statements, we’ll add this code:

jest.mock('react', () => ({
	...jest.requireActual('react'),
	useState: jest.fn()
}))
const setState = jest.fn()

Next, at the top of our test suite, we’ll add this beforeEach() block. This will run clean up and reset our state before each test runs.

describe('Button', () => {
    beforeEach(() => {
		useStateMock.mockImplementation((init: any) => [init, setState])
	})

As you can see, we’re using useStateMock as an alias which we pulled in at the top of our file when we imported useState. From useState we’re calling mockImplementation().

This function accepts a function that will be used as an implementation of the mock for one call to the mocked function. Okay but what does that mean exactly? We’re writing a function inside the mockImplementation() function. The first parameter is init and we’ll return this array that calls init, and setState (a mock jest function).

What does that actually do? Well, in this instance, it’s calling the use of useState in our Button component once for each time we use useState.

Testing the default state

In our second test, let’s test the state of our button when the state is false, or in other words its default state:

    it('renders the button not as disabled when state is false', () => {
        useStateMock.mockImplementationOnce(() => [false, setState])
        render(<Button label="poopy" icon="icons" />)
        expect(screen.getByRole('button', {name: 'poopy'})).not.toBeDisabled()
    })

We can actually write this test like this below and it will produce the same result because the use of this mockImplementationOnce() function is calling the default state of the component.

    it('renders the button not as disabled when state is false', () => {
        useStateMock.mockImplementationOnce((init: any) => [init, setState])
        render(<Button label="poopy" icon="icons" />)
        expect(screen.getByRole('button', {name: 'poopy'})).not.toBeDisabled()
    })

Note that in this test our assertion is testing that the button is not disabled.

Testing the state change

In the next test we’re going to change the first value in the return statement of the mockImplementationOnce() function to true. Then we’ll expect our button to be disabled since the disabled button attribute is tied to this piece of state.

    it('renders the button as disabled when state is true', () => {
        useStateMock.mockImplementationOnce(() => [true, setState])
        render(<Button label="poopy" icon="icons" />)
        expect(screen.getByRole('button', {name: 'poopy'})).toBeDisabled()
    })

Conclusion

That’s it! Hopefully now you have a good understanding of how to test React useState with Jest. We ran through how to test props on a React component. We covered how to actually use React useState in a test with jest.mock and jest.requireActual. And finally you got to see how to use mockImplementationOnce() to dictate the state of your component within your test.

Photo by Rachel on Unsplash