Labs ICT
โญ Pro Login

Testing Async Code

Handling promises and async/await in tests.

2 min read | Unit Testing Tutorial
โญ

Want the full learning experience?

Get structured courses, certificates, projects, and instructor support with LabsICT Pro.

Explore Pro Courses

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');
});
    

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

๐Ÿงช Quick Quiz

How do you test an async function that returns a promise?