Testing Library exposes each query as getBy*, queryBy* and findBy* (plus the All variants). What does each return or throw, and which one do you use to assert that an element is NOT on the page?
answer
- prefix decides the failure mode
- only one variant returns null
- absence assertions need the null-returning one
- getBy also throws on multiple matches
- All variants differ on zero
basics
~20 sgetBy* returns the single match and throws if there is none (or more than one); queryBy* returns null instead of throwing; findBy* returns a promise that retries. Assert absence with queryBy*, because getBy* throws before your matcher can run.
solid answer
~40 sThe three prefixes differ only in failure behaviour. `getBy*` returns the matching element and throws a descriptive error — including a printed DOM — when nothing matches, which makes it the default for anything you expect to be present. `queryBy*` is identical except it returns `null` when nothing matches, so it is the one variant you can safely put inside a negative assertion such as `expect(screen.queryByRole('alert')).not.toBeInTheDocument()`. `findBy*` returns a promise for things that appear later. The `All` variants change arity: `getAllBy*` returns an array and still throws on zero matches, while `queryAllBy*` returns an empty array. The common bug is `expect(screen.getByText('Error')).not.toBeInTheDocument()` — that throws inside the query, so the assertion never runs and the failure message is confusing.
code
javascript · 18 linesimport { screen } from '@testing-library/dom'
document.body.innerHTML = `
<ul>
<li>Apples</li>
<li>Pears</li>
</ul>
`
// present: getBy gives a printed DOM on failure
screen.getByText('Apples')
// absent: queryBy returns null instead of throwing
console.log(screen.queryByText('Bananas')) // null
// counting: All variants differ on zero matches
console.log(screen.getAllByRole('listitem').length) // 2
console.log(screen.queryAllByRole('alert').length) // 0, no throwgo deeper
Memorise the mapping: getBy throws, queryBy returns null, findBy returns a promise. Know that a not-in-the-document assertion must use queryBy.
Explain both failure modes of getBy — zero matches and multiple matches — and why the throw is a feature: an ambiguous query is a test that could quietly assert on the wrong element.
Argue for the failure message as a design concern: choosing getBy for presence gives the next engineer a printed DOM and the available roles when the test breaks at 2am, while queryBy gives them "received null".
Frame it as suite-wide diagnosability — conventions about which variant goes where, and how a codebase that reaches for queryBy everywhere ends up with failures nobody can triage without rerunning locally.
## Two axes, not six queries Every Testing Library query name is built from two independent choices. The **prefix** decides what happens when the element is not there, and the **All** segment decides whether you get one element or a list. `getByRole`, `queryAllByText`, `findByLabelText` are all the same underlying matcher with different failure and arity behaviour. ## getBy* `getByRole('button', { name: 'Save' })` returns the single matching element. It throws if there are **zero** matches, and it also throws if there is **more than one** — an important detail people forget. The zero-match error prints the rendered DOM and, for role queries, the list of roles that were actually available, which is why `getBy*` is the right default: when it fails you learn why immediately. ```javascript const save = screen.getByRole('button', { name: 'Save' }) // throws "Unable to find an accessible element with the role \"button\" // and name \"Save\"" plus the printed DOM ``` Because it throws, `getBy*` doubles as an assertion. `screen.getByText('Saved')` on its own already fails the test if the text is missing — the `expect(...).toBeInTheDocument()` that usually follows is documentation more than logic. ## queryBy* `queryByRole` behaves identically on success but returns `null` on zero matches. It still throws on multiple matches. There is exactly one situation that needs it: asserting something is **absent**. ```javascript // correct expect(screen.queryByRole('alert')).not.toBeInTheDocument() // broken: getBy throws before expect() ever sees a value expect(screen.getByRole('alert')).not.toBeInTheDocument() ``` The broken version fails with "Unable to find an accessible element with the role alert" — technically the outcome you wanted, but reported as an error inside the query rather than a clean assertion failure, and it fails in exactly the case where the code is correct. That inversion is why it is a classic interview trap. Using `queryBy*` for a presence check is the opposite mistake: `expect(screen.queryByText('Saved')).toBeInTheDocument()` works, but when it fails you get "received value must be an HTMLElement... received null" instead of the printed DOM. Prefer `getBy*` whenever you expect the element to exist. ## findBy* `findBy*` returns a promise that resolves with the element once it appears, rejecting after a timeout. It exists for content that arrives after an await point. Two rules matter here: it must always be awaited, and there is no `findBy` equivalent for absence — you cannot "wait for nothing to be there" with a query alone. ## The All variants `getAllByRole('listitem')` returns an array of every match and throws when there are none. `queryAllByRole('listitem')` returns `[]` when there are none and never throws. So the natural way to assert on a count is: ```javascript expect(screen.getAllByRole('listitem')).toHaveLength(3) // list should exist expect(screen.queryAllByRole('alert')).toHaveLength(0) // list may be empty ``` A subtle consequence: `getAllBy*` is also how you legitimately handle several matching elements. If `getByRole('button', { name: /delete/i })` throws "found multiple elements", switching to `getAllBy*` and indexing is one option — usually a worse one than scoping the query, because an index encodes ordering the component is free to change. ## A decision rule you can say out loud - Expect it to be there now → `getBy*`. - Expect it to be absent → `queryBy*`. - Expect it to arrive later → `findBy*`, awaited. - Expect several → the `All` variant, with `getAllBy*` when at least one must exist and `queryAllBy*` when zero is a legal outcome. That rule covers essentially every query you will write, and it is what an interviewer is listening for. The follow-up is almost always "so what happens if two things match `getBy`?" — the answer is that it throws, deliberately, because an ambiguous query is a test that could silently start asserting on the wrong element.
- Why does getBy* throw when two elements match, instead of returning the first one?Because silently returning the first match would let a test drift onto the wrong element after a UI change and still pass. Throwing forces you to disambiguate deliberately — by scoping the query to a region, or by making the accessible names distinct. It is a design choice that trades convenience for the test continuing to mean what you wrote.
- If getBy* already throws when an element is missing, why do people still write expect(...).toBeInTheDocument()?Mostly readability: the assertion states the intent for the next reader and keeps the test's arrange-act-assert shape intact. It adds no real checking power for a presence case. The one place it is load-bearing is the negative form, where `queryBy*` returns null and the matcher is what turns that null into a pass or a failure.
- How would you assert that a list rendered no items at all?Use the query-all variant: `expect(screen.queryAllByRole('listitem')).toHaveLength(0)`. `getAllByRole` would throw on zero matches, turning the expected outcome into an error. If the empty state renders a message instead, asserting on that message with `getByText` is usually the better test — it checks what the user actually sees.
saying these in an interview costs you the question
- Uses getBy inside an expect(...).not.toBeInTheDocument() assertion
- Thinks queryBy is the asynchronous variant
- Believes getBy returns an array when several elements match
- Wraps a query in try/catch to test that something is absent
- Says queryAllBy throws when nothing matches