Core Concepts & Query API
Testing Library: Core Concepts & Query API @testing-library is a family of packages for testing UI components. The philosophy: test the way users use your softw…
Testing Library: Core Concepts & Query API
@testing-library is a family of packages for testing UI components. The philosophy: test the way users use your software — through accessible roles, visible text, and labels — not implementation details.
The Three Guiding Principles
Test what users see: query by role, label, and text — not by CSS class or internal state
Avoid implementation details: don't test which state variables changed or which methods were called
Accessible by default: queries that work for users also work for assistive technologies
Query Variants
Every query strategy comes in three variants:
getBy* Throws if 0 or 2+ elements found. Use when asserting element EXISTS.
queryBy* Returns null if not found. Use when asserting element ABSENT.
findBy* Returns Promise, retries on DOM changes. Use for ASYNC elements.
+ plural versions for multiple elements:
getAllBy* queryAllBy* findAllBy*Query Strategies — Priority Order
1. ByRole — most preferred; queries ARIA roles (reflects accessibility tree)
2. ByLabelText — for form controls with <label>, aria-label, aria-labelledby
3. ByPlaceholderText — form controls; role queries are better when available
4. ByText — for non-interactive elements (headings, paragraphs, divs)
5. ByDisplayValue — currently selected value in input/textarea/select
6. ByAltText — img, area elements with alt attribute
7. ByTitle — elements with title attribute
8. ByTestId — last resort; add data-testid to element manually
Rule of thumb: if a screen reader would find it by role or label, use that query.
If not, consider improving your component's accessibility first.ByRole — Complete Reference
// Common ARIA roles and the HTML elements that map to them:
// Interactive
screen.getByRole('button') // <button>, <input type="button/submit/reset">
screen.getByRole('link') // <a href="...">
screen.getByRole('textbox') // <input type="text">, <textarea>
screen.getByRole('checkbox') // <input type="checkbox">
screen.getByRole('radio') // <input type="radio">
screen.getByRole('combobox') // <select>, or custom combobox with aria-haspopup
screen.getByRole('listbox') // <select multiple>
screen.getByRole('option') // <option>
screen.getByRole('switch') // toggle with role="switch"
screen.getByRole('slider') // <input type="range">
screen.getByRole('spinbutton') // <input type="number">
screen.getByRole('searchbox') // <input type="search">
// Structure
screen.getByRole('heading', { level: 1 }) // <h1> through <h6>
screen.getByRole('list') // <ul>, <ol>
screen.getByRole('listitem') // <li>
screen.getByRole('table') // <table>
screen.getByRole('row') // <tr>
screen.getByRole('cell') // <td>
screen.getByRole('columnheader') // <th>
screen.getByRole('img') // <img> with alt text
// Landmark
screen.getByRole('navigation') // <nav>
screen.getByRole('main') // <main>
screen.getByRole('banner') // <header>
screen.getByRole('contentinfo') // <footer>
screen.getByRole('form') // <form>
screen.getByRole('dialog') // role="dialog" or <dialog>
screen.getByRole('alert') // role="alert" — live region for errors
screen.getByRole('status') // role="status" — live region for status
// Options
screen.getByRole('button', {
name: /submit/i, // accessible name (visible text, aria-label, aria-labelledby)
hidden: true, // include elements with display:none or visibility:hidden
selected: true, // for options
checked: true, // for checkboxes/radios
pressed: true, // for toggle buttons (aria-pressed)
expanded: true, // for expandable elements (aria-expanded)
level: 2, // for headings
})