Testing Async Code
So your code does not just run instantly. It fetches data from an API, reads a file, or waits for something. Testing async code felt intimidating to me at first, but Jest makes it surprisingly straightforward.
Here is the thing. Most real-world code is async. You need to know how to test it or you are stuck testing only trivial functions.
Async/Await
The cleanest way to test async code is with async/await. Just await the result and assert it.
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
it('fetches a user by id', async () => {
const user = await fetchUser(1);
expect(user).toBeDefined();
expect(user.id).toBe(1);
});
Make sure you mark the test function as async. Without that, await will not work and your test will be flaky.
Testing Resolved Values
Jest has a handy resolves matcher that does the async work for you.
it('resolves to the correct value', async () => {
await expect(fetchUser(1)).resolves.toHaveProperty('id', 1);
});
This is shorter but sometimes less readable. Pick whichever style makes more sense for your team.
Testing Rejected Promises
When you expect a promise to reject, use rejects instead of resolves.
async function fetchUser(id) {
if (!id) {
throw new Error('ID is required');
}
return { id, name: 'Alice' };
}
it('rejects when id is missing', async () => {
await expect(fetchUser(null)).rejects.toThrow('ID is required');
});
Handling Timeouts
If your test involves setTimeout or setInterval, use Jest timers to avoid waiting in real time.
jest.useFakeTimers();
function delayedMessage(callback) {
setTimeout(() => callback('done'), 1000);
}
it('calls callback after delay', () => {
const callback = jest.fn();
delayedMessage(callback);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledWith('done');
});
Try it Yourself →
Key Takeaways
- Mark test functions as async when using await inside them
- Use resolves for promises that should succeed
- Use rejects for promises that should fail
- Jest fake timers help you test time-dependent code without real waits
- Always handle both success and failure cases in async tests