With React Native Testing Library, when do you use waitFor instead of a findBy query, and how do you change their timeout per call and globally?
answer
- elements versus arbitrary expectations
- callback must throw until satisfied
- timeout 1000 ms, interval 50 ms
- findBy options go in the third argument
- configure({ asyncUtilTimeout })
basics
~20 sUse findBy* when waiting for an element to appear and waitFor for any other expectation, such as a mock being called. Pass { timeout } per call (findBy's third argument) or set configure({ asyncUtilTimeout }) for the whole suite; the default is 1000 ms.
solid answer
~40 sA `findBy*` query is a `getBy*` wrapped in `waitFor` and resolves with the element, so it is the tool whenever the thing you wait for is an element appearing. `waitFor` is the lower-level primitive for everything else: `await waitFor(() => expect(mockReserveBook).toHaveBeenCalledTimes(1))`, a text change on an element you already hold, a count reaching a value. Its callback must throw until the condition holds; any return value, even `false`, ends the wait. Both check immediately, then every `interval` (50 ms) until `timeout` (1000 ms). Per call, pass `{ timeout, interval }` as `waitFor`'s second argument or as the third argument of a `findBy*` query; putting them in the query-options argument is deprecated and logs a warning. Suite-wide, `configure({ asyncUtilTimeout: 3000 })` changes the default for both, and it should stay below Jest's own per-test timeout.
code
typescript · 10 linesimport { configure, screen, waitFor } from '@testing-library/react-native';
// jest.setup.ts: suite-wide default for waitFor, findBy* and waitForElementToBeRemoved
configure({ asyncUtilTimeout: 2000 });
// in a test: element appearing, with a per-call timeout in the third argument
const title = await screen.findByText('Dune', {}, { timeout: 3000 });
// in a test: a non-element expectation
await waitFor(() => expect(mockReserveBook).toHaveBeenCalledWith('b1'));go deeper
Remember the pairing: findBy for an element appearing, waitFor with an expect inside for anything else, and the 1000 ms default timeout.
Explain the callback contract (throw means retry, any return means done), the 50 ms interval, the third-argument options of findBy, and what asyncUtilTimeout changes.
Treat a timeout as a diagnosis prompt: read the appended tree, check the mock and the match, and keep asyncUtilTimeout below Jest's test timeout so failures stay readable.
Set a suite convention for timeouts: one global default in the setup file, per-call overrides only with a stated reason, and lint rules so waits are always awaited.
## Two tools, one engine **React Native Testing Library (RNTL)** has one waiting engine, `waitFor`, and a family of queries built on it, `findBy*` and `findAllBy*`. Knowing which to reach for is a standard mid-level question because the wrong choice either produces a worse error message or a test that passes without checking anything. `waitFor(expectation, options)` calls `expectation` straight away. If it **throws**, RNTL remembers the error and tries again every `interval` milliseconds. As soon as a call **does not throw**, the returned promise resolves with that call's return value. If `timeout` passes first, the promise rejects with the last error the callback threw, or with "Timed out in waitFor." when no attempt ever produced an error, for example because an async callback's promise never settled. A `findBy*` query is exactly `waitFor(() => getBy*(...))`, with two conveniences: it resolves with the matched element, and on timeout it appends the rendered element tree to the error so you can see what was on screen instead. ## When to use which | You are waiting for | Use | |---|---| | An element to appear | `findBy*` / `findAllBy*` | | A mock to be called, or called with certain arguments | `waitFor` + `expect(...)` | | An element you already hold to change text or props | `waitFor` + a matcher | | An element to disappear | `waitForElementToBeRemoved`, or `waitFor` + `queryBy*` | In the catalog app, the loaded list is a `findByText('Dune')`; checking that pressing Reserve called the mocked `reserveBook` API is a `waitFor(() => expect(mockReserveBook).toHaveBeenCalledWith('b1'))`. Writing `waitFor(() => screen.getByText('Dune'))` works, but it is the long-hand form of a `findBy*` query with a less helpful failure. ## The callback contract `waitFor` decides "not yet" only by a **thrown error**. That has consequences: - `await waitFor(() => mockReserveBook.mock.calls.length === 1)` resolves on the first check, because returning `false` is not a failure. - An empty callback, `waitFor(() => {})`, resolves immediately and waits for nothing meaningful. - Use `expect(...)` or a `getBy*` query inside the callback so a miss throws. - Keep one assertion per `waitFor`; once the first async condition holds, the following assertions usually need no waiting at all. ## Changing the timeout and interval The defaults are `timeout: 1000` and `interval: 50`, both in milliseconds. There are three places to change them: 1. **Per `waitFor` call**, as the second argument: `await waitFor(() => expect(mockReserveBook).toHaveBeenCalled(), { timeout: 3000 })`. 2. **Per `findBy*` call**, as the **third** argument, after the query options: `await screen.findByText('Dune', {}, { timeout: 3000 })`. RNTL still reads `timeout` and `interval` from the second argument, but logs a deprecation warning telling you to move them. 3. **Suite-wide**, with `configure({ asyncUtilTimeout: 3000 })`, typically in a Jest setup file. It changes the default timeout of `waitFor`, every `findBy*` query and `waitForElementToBeRemoved`; `resetToDefaults()` restores it. There is no global setting for the interval. Both `waitFor` and `findBy*` also accept `onTimeout`, a callback that receives the timeout error and can return a replacement; `onTimeout: () => { screen.debug(); }` prints the tree while debugging. ## Reading the failure The two tools fail differently, which is part of why the choice matters: - A `findBy*` timeout reports the query's "Unable to find an element…" message **plus the rendered element tree**, so you see at once whether the screen shows an error state, a spinner, or the right data under slightly different text. - A `waitFor` timeout rethrows **the last error your callback threw**. With `expect(...)` inside, that is Jest's usual expected-versus-received diff, which is the most useful message for a mock-call check. - Neither reports anything useful if the callback never throws, because then it never fails at all. ## How not to "fix" a timeout - Raising `asyncUtilTimeout` past **Jest's per-test timeout** (5 seconds unless configured) means Jest kills the test first, and you lose RNTL's detailed error for a generic one. - A larger timeout hides a real problem when the element never appears for a reason unrelated to speed: a wrong mock, a text mismatch, a failed request rendering an error state. Read the appended element tree before raising anything. - Lowering `interval` rarely helps; a 50 ms poll is already much finer than the delays a mocked API introduces. ## Summary Reach for `findBy*` whenever the condition is "an element is on screen", and for `waitFor` with an `expect` inside for every other condition. Keep the default 1000 ms unless the component really schedules longer work, and when you change it, do so per call for the one slow screen and with `asyncUtilTimeout` only for a suite-wide reason.
- What does waitFor resolve with, and why can that be useful?It resolves with the return value of the first callback call that did not throw. That lets you write `const row = await waitFor(() => screen.getAllByRole('button', { name: /Reserve/ })[0])` style helpers, although for elements a `findBy*` query is usually clearer.
- A findBy query times out and the error shows the book titles in the printed tree. What do you check first?The match itself, not the timeout: RNTL appended the tree because the query never matched. Look for a text mismatch (extra whitespace, a different string, text split across nested Text), or a query that matches more than one element, which also fails a single-element findBy. Raising the timeout would not help.
- Does configure({ asyncUtilTimeout }) also change the interval?No. `asyncUtilTimeout` is only the default `timeout`. The interval stays 50 ms unless you pass `interval` on an individual `waitFor` or `findBy*` call.
saying these in an interview costs you the question
- waitFor(() => mock.mock.calls.length === 1) retries until the count reaches one.
- A findBy timeout belongs in the second argument next to exact and normalizer.
- configure({ asyncUtilTimeout }) sets Jest's timeout for each test.
- waitFor(() => screen.getByText(x)) is better than a findBy query.
- When a findBy query times out, the first fix is a bigger timeout.