How do you test asynchronous code?
JavaScriptThe short answer
To test async code, you use async/await in your test functions, mock API calls with jest.fn() or msw, and use utilities like waitFor and findBy queries from React Testing Library. The key is making sure your test waits for the async operation to complete before making assertions.
Testing async functions with Jest
Use async/await in your test:
async function fetchUser(id) { const response = await fetch(`/api/users/${id}`); return response.json();}test('fetches user data', async () => { global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ id: 1, name: 'John' }), }) ); const user = await fetchUser(1); expect(user.name).toBe('John');});Important: If you forget async/await, the test passes before the promise resolves, and you get a false positive.
Testing async React components
Use findBy queries (which wait automatically) or waitFor:
function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { fetch(`/api/users/${userId}`) .then((res) => res.json()) .then(setUser); }, [userId]); if (!user) return <p>Loading...</p>; return <h1>{user.name}</h1>;}test('displays user name after loading', async () => { global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ name: 'John' }), }) ); render(<UserProfile userId={1} />); // findByText waits for the element to appear expect( await screen.findByText('John') ).toBeInTheDocument();});Using waitFor
waitFor repeatedly checks an assertion until it passes or times out:
test('removes item after delete', async () => { render(<TodoList />); fireEvent.click(screen.getByText('Delete')); await waitFor(() => { expect( screen.queryByText('Buy milk') ).not.toBeInTheDocument(); });});Testing timers
For code that uses setTimeout or setInterval, use fake timers:
test('calls callback after delay', () => { jest.useFakeTimers(); const callback = jest.fn(); delayedCall(callback, 1000); expect(callback).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); expect(callback).toHaveBeenCalledTimes(1); jest.useRealTimers();});Fake timers let you control time in your tests instead of actually waiting.
Common Pitfalls
The most common mistake with async tests is not waiting for the async operation to complete. The test runs the assertion before the promise resolves, gets the initial state (like "Loading..."), and passes even though the final state might be wrong. Always use await, findBy, or waitFor to wait for the async operation.
Interview Tip
Show that you know multiple approaches: async/await for function tests, findBy and waitFor for React component tests, and fake timers for time-dependent code. The most important thing to communicate is that you understand the timing problem — tests run synchronously by default, so you need to explicitly wait for async operations.
Why interviewers ask this
Almost every frontend application has async code — API calls, timers, animations. Interviewers want to see if you can test these correctly without flaky tests. A candidate who knows about waitFor, fake timers, and the common pitfalls shows they have written real tests for real applications.