When I first started writing tests for my React applications, I faced a common dilemma: how do I effectively test user interactions without over-relying on implementation details? I had used Enzyme and even Jest with DOM APIs, but they often led to brittle tests that broke after the slightest UI change. That’s when I discovered React Testing Library (RTL) — and everything changed.
React Testing Library encourages testing from the user’s perspective, focusing on how the UI behaves rather than how it’s implemented. But one hurdle I still had to cross was mastering the various query methods RTL offers — from getBy
to findBy
to queryAllBy
. I used to ask myself questions like: When should I use getByText
vs queryByText
? or Why does findByRole
sometimes throw a timeout error?
If you’ve ever found yourself lost in the sea of RTL queries, don’t worry. In this post, I’ll walk you through the top 10 React Testing Library queries every React developer should have in their toolbox — with explanations, real-world use cases, and even some gotchas.
🔍 1. getByText
The most intuitive and widely-used query, getByText
helps you find elements that contain specific visible text.
const heading = screen.getByText('Welcome to Decode Fix');
expect(heading).toBeInTheDocument();
Use this when the text content must exist. If the element isn’t found, the test fails immediately.
🟡 Gotcha: This fails if the text is dynamic and hasn’t rendered yet. In that case, prefer findByText
.
🔄 2. findByText
This one is async. It waits for the text to appear before asserting, which is perfect for testing data-fetching UIs.
const welcomeMsg = await screen.findByText(/fetching complete/i);
expect(welcomeMsg).toBeInTheDocument();
👉 I used this in a React quiz app I built, where the text updated only after the user submitted answers.
🕵️ 3. queryByText
If you want to check whether something does not exist, queryByText
is your go-to query.
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
It returns null
if not found — without throwing an error.
✅ Use this in conditional rendering scenarios.
👀 4. getByRole
The getByRole
query is super semantic and accessible — highly recommended when testing buttons, headings, checkboxes, etc.
const submitBtn = screen.getByRole('button', { name: /submit/i });
fireEvent.click(submitBtn);
This mirrors how users with screen readers interact with the UI. You can even test dialog roles and alerts for accessibility compliance.
📌 Tip: Use browser DevTools + axe-core
or Accessibility Insights to inspect roles easily.
🧪 5. getByLabelText
Perfect for testing form inputs. It selects an element based on the <label>
text.
const emailInput = screen.getByLabelText(/email address/i);
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
This is way more resilient than querying by placeholder
.
✅ Bonus: It improves your code’s accessibility too.
📦 6. getByPlaceholderText
If your input fields don’t have labels (though they should), you can still use this.
const searchBox = screen.getByPlaceholderText('Search products...');
While useful, I treat this as a fallback — semantic labels always win.
🧑🤝🧑 7. getAllByRole
Use this when you expect multiple elements with the same role.
const listItems = screen.getAllByRole('listitem');
expect(listItems).toHaveLength(3);
Great for testing lists, menus, or tables. If you’re building custom table UIs like this React table design, this query is a must.
🧵 8. within()
within
helps scope your query to a specific section of the DOM.
const modal = screen.getByRole('dialog');
const closeButton = within(modal).getByRole('button', { name: /close/i });
fireEvent.click(closeButton);
This is a lifesaver when testing components that render nested structures like modals, tabs, or sidebars.
🌟 Internal guide: For better modular UI development, check out this guide to Storybook in React.
⌛ 9. findAllByText
Like findByText
, but expects multiple matches. It returns a Promise that resolves once all elements appear.
const alerts = await screen.findAllByText(/error/i);
expect(alerts).toHaveLength(2);
Useful for testing form validation, flash messages, or repeated dynamic text.
📃 10. getByTestId
Not the most elegant, but practical. When other queries don’t work due to lack of semantics or dynamic content, use data-testid
.
const banner = screen.getByTestId('promo-banner');
expect(banner).toBeVisible();
However, overusing getByTestId
can lead to tightly coupled tests. Use it sparingly and only when necessary.
Bonus Tips 🧠
✅ Combine Queries with within
const nav = screen.getByRole('navigation');
const link = within(nav).getByRole('link', { name: /home/i });
It helps keep tests clean and scoped, especially when your app layout grows complex.
🧰 Use Tools to Inspect the DOM
Use utilities like:
- Testing Playground
screen.logTestingPlaygroundURL()
debug()
from RTL
These help visualize the rendered DOM and choose the best query method.
🔁 Want to Practice These?
I highly recommend trying out quizzes like this ReactJS interview quiz to test your theoretical and practical React knowledge.
Final Thoughts 💬
Testing is not just about making things “pass.” It’s about writing tests that fail for the right reasons and guide you toward better architecture. The React Testing Library queries we explored today not only help you write robust tests but also align your mindset with accessibility and user-centric design.
When I started treating my tests like a user interacting with the app — instead of a dev poking through props — everything clicked. I stopped mocking everything and started trusting my UI more. And that’s where the real confidence comes from.
If you’re building React apps — whether they’re small tools like image to base64 converters or large-scale enterprise dashboards — knowing how to write clean, user-focused tests will be your superpower.
Happy testing! 🧪💙