How do you test asynchronous code?

JavaScript

The 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.