🧪 Simplifying Your Tests with React Testing Library
Welcome back to your journey in testing React applications! Previously, we built a test using ReactDOM
, manually creating DOM nodes, appending them to the body, cleaning up, and wrapping interactions in act()
. It worked—but it was a bit clunky.
Now it’s time to streamline and modernize your test setup with a powerful tool designed specifically for this purpose: React Testing Library.
React Testing Library (RTL) is the React-specific implementation of the DOM Testing Library, which also powers tools for Angular, Vue, React Native, and more.
RTL encourages best practices by helping you test components in a way that’s closer to how users interact with them. It removes boilerplate, automatically handles cleanup, and wraps everything in act()
behind the scenes.
Let’s explore how this library can help you write cleaner, more readable, and more robust tests.
In our earlier test, we had to:
div
and append it to the document.body
createRoot
act()
for every interactionquerySelector()
It worked, but all of that setup distracted us from what really matters: testing user behavior.
Let’s simplify everything using RTL.
import { render, fireEvent, screen } from '@testing-library/react'import Counter from '../counter'test('counter increments and decrements', () => {render(<Counter />)const incrementButton = screen.getByText('Increment')const decrementButton = screen.getByText('Decrement')const message = screen.getByText(/current count/i)fireEvent.click(incrementButton)expect(message).toHaveTextContent('Current count: 1')fireEvent.click(decrementButton)expect(message).toHaveTextContent('Current count: 0')})
Let’s break down what happened here:
✅ No need for createRoot
, act
, or cleanup
— RTL handles that for you
✅ We use screen queries (like getByText
) which resemble how users find things
✅ Assertions use toHaveTextContent
from @testing-library/jest-dom
— more expressive and helpful
✅ The test is shorter, clearer, and more maintainable
screen
and fireEvent
screen
is the global query interface provided by RTL. Instead of destructuring results from render()
, you can use screen.getByText
, screen.getByRole
, and other queries to search the DOM like a user would.fireEvent
simulates user interactions like clicking, typing, or focusing.🧠 Tip: RTL also supports
userEvent
, which simulates interactions more realistically. We’ll explore that later!
@testing-library/jest-dom
for Better AssertionsYou’re not limited to Jest’s built-in assertions. RTL comes with @testing-library/jest-dom, a set of custom matchers that give you more expressive test code and clearer error messages.
For example, instead of writing:
expect(message.textContent).toBe('Current count: 1')
Write this:
expect(message).toHaveTextContent('Current count: 1')
Benefits:
Here’s what we accomplished today:
Old Way (ReactDOM ) | New Way (React Testing Library) |
---|---|
Manual DOM setup | render() abstracts setup |
act() everywhere | Wrapped for you automatically |
textContent checks | toHaveTextContent matcher |
Cleanup required | Auto cleanup between tests |
Low-level selectors | High-level queries (getByText ) |
Want to lock in what you just learned? Take a moment to reflect and submit your thoughts in this form:
Now that you’ve seen how React Testing Library simplifies your tests, you’re ready to explore user interactions more deeply using tools like userEvent
, and learn to test more complex components involving context, state, and async behavior.
Remember: test the way users use your app, and you’ll build more confident, resilient software.
Quick Links
Legal Stuff
Social Media