Skip to Content
Course content

123: Testing Asynchronous Code

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Why is my test passing even though the assertion is clearly failing?

This is the most frustrating part of starting with async testing. You write a test, you intentionally break the code to make sure the test catches it, and... it still passes. You'll feel like you're losing your mind, but what's actually happening is a "false positive."

In a synchronous test, the runner executes line by line. But with async code, if you don't tell the test runner to wait, it just triggers the promise and immediately moves to the next line. If there are no more lines, the test finishes and marks itself as "passed" before the promise ever actually resolves or throws an error.

// ❌ The "False Positive" Trap
test('fetches user data', () => {
  // This function is async, but we aren't awaiting it
  fetchUser(1).then(user => {
    expect(user.name).toBe('Alice'); // This runs AFTER the test is already "done"
  });
  // The test ends right here, and the runner thinks everything is fine.
});

I've spent way too many hours debugging "passing" tests that were actually just ignoring the errors. The fix is simple: you have to make the test function itself asynchronous.

How do I actually make the test wait for the result?

The modern standard is to use async/await. It makes your test look and feel like synchronous code, which is much easier to read and maintain. You just mark the test callback as async and await the result of your function call.

Let's say we have a function that fetches a user's profile from an API. Here is how you handle that properly:

// ✅ The Correct Way
test('fetches user data successfully', async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe('Alice');
  expect(user.email).toBe('alice@example.com');
});

Now, the test runner knows that it needs to pause and wait for fetchUser to resolve before it proceeds to the expect statements. If the promise rejects, the test runner will catch that rejection and correctly mark the test as failed.

What do I do when I'm testing code with a long delay or a timeout?

If you're testing a function that uses setTimeout—like a debounced search input or a session timeout—you obviously don't want your test suite to actually wait 5 seconds every time it runs. That's how CI pipelines become painfully slow.

Most testing frameworks (like Jest or Vitest) provide "fake timers." Instead of waiting for real time to pass, you tell the framework to "fast-forward" the clock. I use this all the time for testing things like automatic logout timers.

// Testing a function that triggers a callback after 5 seconds
test('triggers timeout alert after 5 seconds', () => {
  jest.useFakeTimers(); // Switch to fake timers
  const callback = jest.fn();
  
  startTimer(5000, callback);

  // Fast-forward time by 5 seconds instantly
  jest.advanceTimersByTime(5000);

  expect(callback).toHaveBeenCalledTimes(1);
  jest.useRealTimers(); // Always clean up!
});

Just a heads-up: remember to call useRealTimers() at the end. If you don't, other tests in your suite might behave weirdly because they're still operating on a frozen clock.




📋 Practical Task

Fixing the Flaky Weather API Test

You've been handed a test file for a getWeather(city) function. The function is asynchronous, but the current test is a "false positive"—it passes even when the city is invalid because it isn't awaiting the promise.

Your task: Modify the test code below to correctly await the getWeather call so that the test accurately fails if the returned temperature is incorrect.


async function getWeather(city) {
  // Simulates an API call
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ city: 'London', temp: 15 });
    }, 100);
  });
}

// ❌ FIX THIS TEST
test('returns the correct temperature for London', () => {
  getWeather('London').then(data => {
    expect(data.temp).toBe(15);
  });
});
Rating
0 0

There are no comments for now.

to be the first to leave a comment.