How do you test asynchronous code, timers, and error paths reliably?

3 minadvancednodejsasync-testingfake-timersflaky-testspromises

Quick Answer

Return or await the promise so the runner waits for completion; assert rejections with expect(...).rejects.toThrow. Replace real time with fake timers to test timeouts/intervals/debounce without waiting. Keep tests deterministic and isolated to avoid flakiness — no real network, no shared state, no reliance on wall-clock timing.

Detailed Answer

Answer:

Async tests — make the runner wait:

// async/await (preferred)
test('fetches a user', async () => {
  const user = await getUser(1);
  expect(user.name).toBe('Alice');
});

// Assert a rejection
test('throws on missing user', async () => {
  await expect(getUser(999)).rejects.toThrow('Not found');
});
  • The common bug is a test that doesn't await the promise — it passes even when the assertion would fail, because the test finishes before the async work. Always await (or return) the promise.

Timers — don't actually wait: Use fake timers to control time deterministically (fast, no flakiness):

jest.useFakeTimers();

test('debounce fires once after 300ms', () => {
  const fn = jest.fn();
  const debounced = debounce(fn, 300);
  debounced(); debounced();
  jest.advanceTimersByTime(300);  // simulate time passing
  expect(fn).toHaveBeenCalledTimes(1);
});

This tests timeouts, intervals, and debounce/throttle instantly.

Avoiding flaky tests:

  • No real network/clock — mock external calls, use fake timers instead of setTimeout waits.
  • No shared state — reset mocks and data in beforeEach; don't depend on test order.
  • Deterministic data — seed randomness, fix "now" (fake timers / injected clock).
  • Await everything — unhandled async is the #1 cause of intermittent failures.
  • Assert on outcomes/state, not on arbitrary sleep durations.

Error paths: force failures by stubbing a dependency to throw/reject, then assert the code handles it (returns the right status, logs, retries, rolls back). Error branches are exactly where bugs hide, so test them explicitly.