This error occurs when using React Testing Library's queryBy* methods with async assertions like waitFor or findBy*. queryBy* queries are designed for synchronous checks and return null when elements aren't found, while findBy* and getBy* queries handle async operations properly. Understanding the difference between query types prevents this common testing mistake.
React Testing Library provides three main query types for finding elements: getBy*, queryBy*, and findBy*. Each serves a different purpose in testing scenarios. The error "You gave screen a query of type "query" but only "find" or "get" queries can be used with asynchronous assertions" occurs when you attempt to use a queryBy* method within an asynchronous assertion context. queryBy* queries are specifically designed for synchronous operations where you want to check if an element exists or doesn't exist without throwing an error. They return null when elements aren't found, making them unsuitable for async assertions that expect the query to wait for elements to appear. Async assertions like waitFor, findBy*, or async matchers require queries that will either find the element or throw an error, which is why only getBy* (synchronous error-throwing) and findBy* (async error-throwing) queries are allowed in these contexts.
When you need to wait for an element to appear asynchronously (e.g., after API calls, state updates, or animations), use findBy* queries instead of queryBy*. findBy* queries combine getBy* with waitFor internally, automatically waiting for the element to appear.
// WRONG - queryBy* in waitFor
await waitFor(() => {
expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
});
// CORRECT - findBy* for async waiting
const loadingElement = await screen.findByText("Loading...");
expect(loadingElement).toBeInTheDocument();
// Also CORRECT - waitFor with getBy*
await waitFor(() => {
expect(screen.getByText("Loading...")).toBeInTheDocument();
});If you need to use waitFor explicitly, use getBy* queries inside it. getBy* queries throw errors when elements aren't found, which works correctly with waitFor's retry mechanism.
// WRONG
await waitFor(() => {
expect(screen.queryByRole("button", { name: "Submit" })).toBeEnabled();
});
// CORRECT
await waitFor(() => {
const button = screen.getByRole("button", { name: "Submit" });
expect(button).toBeEnabled();
});
// Also CORRECT - findBy* is simpler
const button = await screen.findByRole("button", { name: "Submit" });
expect(button).toBeEnabled();Use queryBy* only when you need to check if an element is NOT present, or when you want to conditionally assert without throwing errors. These are synchronous operations that don't involve waiting.
// CORRECT - queryBy* for checking element absence
const submitButton = screen.queryByRole("button", { name: "Submit" });
expect(submitButton).not.toBeInTheDocument();
// CORRECT - queryBy* for conditional logic
const errorMessage = screen.queryByText("Error occurred");
if (errorMessage) {
// Handle error case
expect(errorMessage).toHaveClass("error");
}
// WRONG - queryBy* with async waiting
await waitFor(() => {
expect(screen.queryByText("Success!")).toBeInTheDocument();
});Memorize the purpose of each query type to prevent future errors:
- getBy*: Use when you expect the element to be present NOW. Throws error if not found. Good for synchronous assertions.
- queryBy*: Use when checking if element is NOT present, or for conditional logic. Returns null if not found. Synchronous only.
- findBy*: Use when waiting for element to appear. Returns Promise that resolves when found, rejects if timeout. Async version of getBy*.
// Synchronous - element should be present
expect(screen.getByText("Welcome")).toBeInTheDocument();
// Synchronous - element should NOT be present
expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
// Asynchronous - wait for element to appear
const data = await screen.findByTestId("user-data");
expect(data).toHaveTextContent("John Doe");Update these common patterns that incorrectly use queryBy*:
// Pattern 1: Waiting for element to disappear
// WRONG
await waitFor(() => {
expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
});
// CORRECT - waitFor with getBy* and .not
await waitFor(() => {
expect(screen.getByText("Loading...")).not.toBeInTheDocument();
});
// Pattern 2: Multiple async checks
// WRONG
await waitFor(() => {
expect(screen.queryByText("Step 1")).toBeInTheDocument();
expect(screen.queryByText("Step 2")).toBeInTheDocument();
});
// CORRECT - findBy* for each or waitFor with getBy*
const step1 = await screen.findByText("Step 1");
const step2 = await screen.findByText("Step 2");
expect(step1).toBeInTheDocument();
expect(step2).toBeInTheDocument();
// OR
await waitFor(() => {
expect(screen.getByText("Step 1")).toBeInTheDocument();
expect(screen.getByText("Step 2")).toBeInTheDocument();
});Ensure your tests properly handle async operations and errors:
// WRONG - missing async/await
it("should load data", () => {
userEvent.click(screen.getByText("Load"));
// This will fail because queryBy* is in waitFor
waitFor(() => {
expect(screen.queryByText("Data loaded")).toBeInTheDocument();
});
});
// CORRECT - proper async/await
it("should load data", async () => {
userEvent.click(screen.getByText("Load"));
// Use findBy* for async waiting
const message = await screen.findByText("Data loaded");
expect(message).toBeInTheDocument();
});
// CORRECT - with try/catch for error cases
it("should handle errors", async () => {
userEvent.click(screen.getByText("Load"));
try {
await screen.findByText("Error occurred", { timeout: 2000 });
// Error appeared as expected
} catch (error) {
// No error appeared within timeout
fail("Expected error message to appear");
}
});The query type system in React Testing Library is designed to make tests more explicit and prevent common mistakes. queryBy* queries return null instead of throwing errors to facilitate "element not present" assertions. This design choice means they cannot be used in async contexts where the test runner needs to distinguish between "element not found yet" (should retry) and "element will never be found" (should fail). Under the hood, findBy* queries use waitFor with getBy*, retrying until the element appears or a timeout occurs. When debugging async test failures, increase default timeouts carefully: screen.findBy*(..., { timeout: 5000 }). For complex async scenarios with multiple dependent elements, consider using waitFor with multiple getBy* assertions instead of multiple findBy* calls to avoid nested async operations. Remember that userEvent methods are async in newer versions of Testing Library, so always use await with them.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite