Labs ICT
Pro Login

Testing Async Code

Handling promises and async/await in tests.

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