In this post, I’ve compiled a set of best practices for using React Testing Library gathered from various articles across the internet. I’ve organized and listed them here to share what I’ve learned with others. All credits belong to the original authors of the articles, and I have included links to the original posts at the bottom of this article.
Using “Screen”
// Bad ❌
const {getByRole} = render(<Example />)
const errorMessageNode = getByRole('alert')
// ✅ - screen always gets the most up-to-date UI
render(<Example />)
const errorMessageNode = screen.getByRole('alert')
The benefit of using screen
is you no longer need to keep the render
call destructure up-to-date as you add/remove the queries you need. You only need to type screen.
and let your editor’s magic autocomplete take care of the rest.
Another benefit is that using screen
makes your tests mimic how users interact with your app more closely.
Using “findBy” over “getBy” for Asynchronous Tests
// <MessageAsync /> asynchronously retrieves data from an API
// and displays content in the UI after approximately 1 second
// Not optimized ❌
test('Should show "example text" after one second', async () => {
render(<MessageAsync />)
await waitFor(() => {
const text = screen.getByText('Example text')
expect(text).toBeInTheDocument()
})
})
// ✅ - use findByText is more readable
test('Should show "example text" after one second', async () => {
render(<MessageAsync />)
const text = await screen.findByText('Example text')
expect(text).toBeInTheDocument()
})
Prefer using the findBy
function instead of using getBy
within a waitFor
block to simplify asynchronous tests. This is because findBy
automatically includes the waiting process for components that are not immediately present in the UI.
Avoid Using “act” with “fireEvent” or “render”
// Not optimized ❌
test('Should increase total in counter, when on press', () => {
act(() => {
render(<Counter />) // act is redundant
})
const button = screen.getByRole('button', { name: 'increase' })
act(() => {
fireEvent.click(button) // act is redundant
})
const counter = screen.getByText('total:')
expect(counter).toHaveTextContent('total: 1')
})
// ✅ Better readibility
test('Should increase total in counter, when on press', () => {
render(<Counter />)
const button = screen.getByRole('button', { name: 'increase' })
fireEvent.click(button)
const counter = screen.getByText('total:')
expect(counter).toHaveTextContent('total: 1')
})
fireEvent
and render
are already designed to handle synchronization internally, so we should avoid redundant use of act
in our code. Although redundantly using act
does not affect the application’s execution, be aware that it does reduce the readability of the code.
Avoid Using “getByTestId”
// Bad ❌
// assuming you've got this DOM to work with:
// <label>Username</label><input data-testid="username" />
screen.getByTestId('username')
// ✅
// change the DOM to be accessible by associating the label and setting the type
// <label for="username">Username</label><input id="username" type="text" />
screen.getByRole('textbox', {name: /username/i})
You should aim to query the DOM in a manner that closely mimics how your end-users interact with it. Using getByTestId
should only be considered as a last resort.
In fact, getByRole
is the number one recommended approach to query your component’s output.
Using “@testing-library/user-event” over “fireEvent” Where Possible
// Not best option ❌
fireEvent.change(input, {target: {value: 'hello world'}})
// ✅
userEvent.type(input, 'hello world')
@testing-library/user-event
is a package that’s built on top of fireEvent
, but it provides several methods that resemble the user interactions more closely. In the example above, fireEvent.change
will simply trigger a single change event on the input. However the type
call, will trigger keyDown
, keyPress
, and keyUp
events for each character as well. It’s much closer to the user’s actual interactions. This has the benefit of working well with libraries that you may use which don’t actually listen for the change event.
Conclusion
I handpicked these five tips because I found them to be the most important ones for me while working on my current projects. With these, I managed to improve my code quality significantly. For the complete list of tips, you can check out the links right below.
References
Mastering in react testing library: 7 tips to optimize your tests