ReviseAlgo Logo

Testing JavaScript

Testing Async Code

Master testing asynchronous JavaScript code. Learn to handle callbacks, test Promises, use async/await inside Jest, and prevent false passes.

Last Updated: July 15, 2026 10 min read

1. Introduction

JavaScript is highly asynchronous, relying on callbacks, Promises, and async/await. Testing asynchronous code requires informing the testing framework to wait for async operations to complete before finishing the test.

2. Why It Matters

If you write an asynchronous test without instructing the runner to wait, the test function will exit immediately, reporting success before the async operation or assertion callback even executes. This is called a false pass (a test that passes even though its assertions fail or throw errors).

3. Real-World Analogy

Think of a Delivery Driver Package Check:

  • Synchronous Test (Fast sign-off): You sign the delivery receipt the moment the driver rings the bell, before opening the door or checking the contents. The driver leaves, and only later do you discover that the package contains broken items. Your check passed immediately but was useless.
  • Asynchronous Test (Waiting at door): You open the door, hold the driver, open the package, check the items (assert), and only then sign the receipt (resolve/done signal). The check waits for the delivery package verification before finishing.

4. Testing Callbacks with done

To test functions that use callbacks, pass the done parameter to the test function. Jest will wait until the done() callback is invoked before completing the test:

5. Testing Promises & Async/Await

Testing Promises is simpler: return the Promise from the test function, or declare the test function as async and use the await keyword inside the assertions:

6. Practical Example

This script demonstrates how to test Promise rejections in Jest:

7. Common Mistakes

  • Forgetting to return Promises inside tests: If you omit the return keyword when testing Promises (and do not use async/await), the test runner will finish immediately, resulting in a false pass. Always return the Promise or use async/await.

8. Quick Quiz

Q1: What happens if an async test is written without returning a Promise, using async/await, or calling done()?

A) The test times out after 5 seconds and fails

B) The test exits immediately, resulting in a false pass before the asynchronous assertions run

Answer: B — Without an async signaling mechanism, the test function completes synchronously, passing immediately before the async callbacks execute.

9. Scenario-Based Challenge

The Async DB Query Tester:

An application performs async queries: db.findUserById(id). If the user is found, it returns a profile object; if missing, it throws a "User not found" error. Write a Jest test suite to test both the successful load and the rejection error conditions.

10. Debugging Exercise

Explain why this test suite falsely passes, and how to fix it:

const slowResolve = () => new Promise((res) => setTimeout(() => res('value'), 100));

test('asserts slow resolve value', () => { // Bug: forgot to return the promise or use async/await! slowResolve().then((val) => { expect(val).toBe('wrong-value'); // This assertion is never evaluated! }); });

View Solution

Diagnosis: The test starts the promise but exits synchronously. By the time the timeout resolves 100ms later and runs the assertion, the test has already reported a successful run. The failed assertion is ignored (false pass).

Fix: Return the Promise or declare the test function as async and use await:

test('asserts slow resolve value', async () => {
  const val = await slowResolve();
  expect(val).toBe('value'); // Evaluated correctly!
});

11. Interview Questions

🟢 Q1: Why is expect.assertions() useful in testing asynchronous JavaScript?

Answer: expect.assertions(number) tells Jest to verify that a specific number of assertions are called during the test.
This is useful when testing asynchronous code (like promise rejections inside catch blocks). If the promise resolves successfully instead of rejecting, the catch block is skipped. Without expect.assertions(), the test would pass silently without running any assertions. Explicitly declaring the assertion count catches these bugs.

12. Production Considerations

  • Timeout Limits: Asynchronous operations can hang if network calls or database triggers freeze. Set explicit timeouts (e.g. jest.setTimeout(5000)) on your tests to fail quickly if a task takes too long, preventing CI/CD pipelines from hanging.