Design Converter
Education
Last updated on Jun 5, 2024
Last updated on Jun 5, 2024
A common scenario while writing tests for a React application with asynchronous code is to handle asynchronous behavior by waiting for some element to be present or visible in the DOM. The React Testing Library offers excellent support for this kind of scenario via its waitFor utility. This lets you wait within a test for a certain condition to be met before it proceeds, failing the test if the waiting timeout is reached.
The render method is the foundational method when writing tests with the React Testing Library. It renders the component you’re testing into the DOM, allowing you to interact with and query it.
1import { waitFor } from "@testing-library/react"; 2 3// Wait for example usage await 4waitFor(() => { 5 // Your code that requires waiting for some condition 6});
waitFor is a utility function provided by the React Testing Library that plays very nicely with React components. However, there is a proper way to use the waitFor function. Let’s dive into how we can avoid flaky tests while using the React Testing Library waitFor utility.
You would typically use the waitFor utility when your React component is making an API call to fetch data from an asynchronous API or resolving a promise. Async methods and async operations are essential in this context, as they handle waiting for elements to appear or disappear, making HTTP calls, and managing state updates during asynchronous rendering. It can also be used to wait for some useEffect hook to complete its operation or simply wait for the DOM to update after a user clicks on an async button method.
1import { render, waitFor } from "@testing-library/react"; 2import MyComponent from "./MyComponent"; 3 4test("should be in the DOM after fetching", async () => { 5 render(<MyComponent />); 6 await waitFor(() => 7 expect(document.querySelector(".updated")).toBeInTheDocument() 8 ); 9});
In the above test, we utilize waitFor to ensure the test runner awaits the existence of .updated in the DOM before executing the expect assertion.
The default timeout for waitFor is 1000 milliseconds or 1 second. If the condition passed in the callback of waitFor does not become truthy within this duration, the waitFor utility throws an error. However, one can override this default timeout by passing a second argument to waitFor.
1await waitFor(() => { 2 // Your callback logic here 3}, { timeout: 5000 }); // Custom timeout of 5000 milliseconds
You would typically pass a larger timeout value when you expect the asynchronous code to take more than the default timeout duration.
A straightforward method to test setTimeout in Jest is by utilizing the jest.runAllTimers or jest.runOnlyPendingTimers methods to immediately run all or only pending timers. This technique is quite useful when you have a component that invokes setTimeout for some asynchronous operation. You can simply utilize jest.useFakeTimers and then call jest.runAllTimers to proceed with the test.
1test("should call setTimeout in the component", () => { 2 render(<MyComponent />); 3 expect(setTimeout).toHaveBeenCalledTimes(1); 4 // Run all timers 5 jest.runAllTimers(); 6 // More assertions can be added here 7});
This technique lets you assert the component behavior without actually waiting for the timeout. Additionally, configuring mock services for API testing within the test file is crucial for ensuring comprehensive test coverage.
The waitFor utility in React Testing Library lets you write tests that deal with asynchronous actions by awaiting a callback that does not return a promise that rejects. Asynchronous behavior in components can be managed using waitFor to wait for an element to appear or change, or when expecting some action to occur after a certain period of time. It invokes the callback repeatedly until it no longer throws an error or the timeout is exceeded. This utility is quite handy when you need to wait for some element to be present or rendered in the DOM, waiting for an API to respond, or any other form of asynchronous code.
1test("wait for should render the element", async () => { 2 const { getByText } = render(<MyComponent />); 3 await waitFor(() => { 4 expect(getByText(/loading/i)).not.toBeInTheDocument(); 5 }); 6});
In the above example, we wait until the loading text is not in the document anymore to proceed with the expect assertion. This indicates that the asynchronous operation has finished and the component has updated to show the result.
Yes, the waitFor utility can be used without await in certain scenarios. Generally, the waitFor utility is used with await to wait within a test for a certain condition to be met before it proceeds. However, await is not applicable when the test function does not return a promise that rejects. Also, there can be scenarios where you want to use the waitFor utility from within a callback that does not support an async function. In these cases, you can omit the await keyword and use the .then() method to handle the resolution of the waitFor utility.
1test("waitFor can be used without await in some cases", () => { 2 const { getByText } = render(<MyComponent />); 3 4 waitFor(() => { 5 expect(getByText(/loaded/i)).toBeInTheDocument(); 6 }).then(() => { 7 // More logic here depending on the waitFor utility 8 }); 9});
In the above test, we use waitFor without await and use the then() method to handle the resolution of the waitFor utility. However, await should be used with utility functions like waitFor as much as possible to avoid unhandled promises.
You can wait for an element to be present in the DOM using the waitFor utility function in combination with a query like getByText or getByTestId, which are examples of an async method. The waitFor utility will repeatedly invoke the query until the element is in the DOM or the timeout expires. This is quite straightforward to understand and utilize in your tests. Let’s dive into some more scenarios to test your asynchronous React component.
1test("waits for the element to be available", async () => { 2 const { getByTestId } = render(<MyComponent />); 3 await waitFor(() => { 4 expect(getByTestId("new-element")).toBeInTheDocument(); 5 }); 6});
In the above test scenario, we wait for an element with the testId “new-element” to be present in the DOM. The expect assertion is utilized within the waitFor callback to ensure the test will not proceed until the element is available in the DOM.
What does it throw if the waitFor utility fails? It throws an error after the timeout duration is exceeded without the callback becoming truthy. The error message typically reads something like the expectation failed: timeout of 1000 elements of text was not met in 5000 ms of argument waiting. This implies that the expected element or condition took longer than the provided timeout to be met. Let’s see how we can handle this failure in a test. The expect utility in Jest lets you assert that a promise got rejected and throw an error. You can use this expect utility within the then callback of the waitFor utility to assert that the promise returned by the waitFor utility gets rejected. See the below example.
1test('handles waitFor failure', async () => { 2 const { queryByText } = render(<MyComponent />); 3 await expect(waitFor(() => { 4 expect(queryByText(/non-existent/i)).toBeInTheDocument(); 5 })).rejects.toThrow(); 6});
In the above test example, we are trying to locate some text that does not exist in the component. The waitFor utility fails, and we force the test to expect that failure by asserting that the promise returned by waitFor gets rejected and throws an error.
Wait is an older API in the React Testing Library that got deprecated and should not be used anymore. However, if you are working with an older version of the React Testing Library, you can utilize the wait function just like the waitFor utility.
Let’s dive into an example. Wait takes a callback as the second argument that should return a promise that rejects or a value that the expect utility can assert. Similar to the waitFor utility, the wait function also takes an options object with a timeout key that should have a numeric value representing the timeout in milliseconds.
Let’s explore one more test with the wait API. Wait utilizes the util.promisify API from Node which turns a function with a callback into a promise returning function. This is quite similar to how you can use the waitFor utility. However, the wait API is deprecated and should not be used anymore due to its limitations.
The waitFor API provides a much more flexible and powerful API to work with. It is highly recommended to use the waitFor API instead of the deprecated wait API. The act function ensures that all updates related to state changes, effects, and other asynchronous actions are flushed before proceeding with tests.
1test("Jest test with async/await and waitFor", async () => { 2 const { getByRole } = render(<MyComponent />); 3 await waitFor(() => { 4 expect(getByRole("alert")).toHaveTextContent(/success/i); 5 }); 6});
In the above example, we wait for an alert to appear in the DOM with a success message.
The await keyword in front of the waitFor utility lets the JavaScript engine wait for the promise returned by waitFor to get resolved. This is a very important concept to understand as we move ahead in writing tests for asynchronous code in modern React applications.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.