All topics
Testingadvanced

Testing Asynchronous Code with fakeAsync

Explain fakeAsync and tick() as tools for deterministically testing code involving timers, promises, or debounced observables.

Testing code involving setTimeout, setInterval, debounceTime, or Promises the naive way requires either real waiting (making tests slow) or awkward, error-prone manual coordination — fakeAsync solves this by running a test inside a special Angular zone that lets you control simulated time explicitly, making genuinely asynchronous code behave deterministically and near-instantly within a test.

It's like a flight simulator that lets a pilot trainee experience an entire multi-hour flight's worth of instrument changes in just a few real minutes by fast-forwarding simulated time, rather than requiring the trainee to sit through an actual real-time flight to practice the same scenario.

Key Concepts

1
Wrapping a test function with fakeAsync(() => {...}) lets you use tick(milliseconds) inside it to synchronously advance the simulated clock by a specified amount, flushing any pending timers, debounceTime buffers, or resolved microtasks that would have fired within that simulated time window — without your test actually waiting that long in real wall-clock time, which is what makes fakeAsync tests both deterministic and fast even when testing code involving multi-second delays.
fakeAsync(() => {...})tick(milliseconds)debounceTimefakeAsync
2
flushMicrotasks() (or the more common tick() with no argument, which defaults to flushing everything pending at the current simulated instant) specifically handles pending Promise resolutions without needing to know or specify an exact delay, which is the right tool when you're waiting on a Promise chain to settle rather than a literal timer duration.
flushMicrotasks()tick()
3
A frequently-tested gotcha: fakeAsync throws an error if the test completes while there are still unresolved pending timers (a common cause being an setInterval that was never cleared, or a tick() call that didn't advance far enough to flush every scheduled callback) — this is deliberately strict, specifically to catch real bugs (like an uncancelled interval) that might otherwise go unnoticed, and the fix is either advancing tick() far enough to let everything settle, or using discardPeriodicTasks() specifically for intentionally-still-running intervals that the test doesn't need to wait out completely.
fakeAsyncsetIntervaltick()discardPeriodicTasks()