Recipe on how to test asynchronous code in Angular applications.

Photo by ilgmyzin, https://unsplash.com/de/fotos/ein-schwarz-weiss-foto-einer-vase-E6Ahvcjddk0

It can be confusing to choose between the different mechanisms available for testing asynchronous code in Angular. With the following recipe, you can quickly choose the right way while keeping your testing code as straightforward as possible:

  1. Start with the simplest setup, without taking asynchronicity into account. In many cases, the test will succeed because there are no real asynchronous invocations in your test environment anyway.
  2. If necessary, wrap your test into Angular’s fakeAsync function.
  3. If necessary, invoke Angular’s flush function inside your fakeAsync test (between the “when” and “then” sections).
  4. If necessary, invoke Angular’s tick function instead of flush, and pass it the number of milliseconds to advance the simulated clock.

The fakeAsync function from the recipe above will not work if your tests perform real HTTP calls (which they usually shouldn’t do anyway). In this case, you will have to use the following recipe instead:

  1. Wrap your test into Angular’s waitForAsync function.
  2. If necessary, invoke Angular’s whenStable function inside your test, and make sure that your assertions run after the completion of the returned promise: Either by executing the assertion inside the promise’s then method, or by declaring your test as async, awaiting the promise, and executing the assertion afterwards.
  3. If necessary, execute the when section’s code inside the run method of your fixture’s NgZone.

Systematic examples for various combinations of testing approaches with code under test can be found on GitHub (angular-async-testing).

The remainder of this post contains some additional background information for supporting the recipe above.

fakeAsync

Simulates the passage of time, and allows the use of the following functions:

  • flushMicrotasks: Flushes pending microtasks.
  • flush: Flushes pending microtasks, and drains the macrotask queue.
  • tick: Advances the simulated time by the given amount of milliseconds.

waitForAsync

Completes when all asynchronous calls are done. This allows executing assertions directly on promises and observables returned by the code under test, and also using the following function:

Compared to fakeAsync, using waitForAsync has the disadvantage that asynchronous delays are executed in real time, resulting in slower test execution. However, it has the advantage that it supports real HTTP calls.

In earlier Angular versions, this function had the name async(), which has lead to confusion with JavaScript’s async function declaration.

“done” callback

Available in Jasmine and Jest, this callback can be used to keep the test from terminating before an assertion has been executed. If you are testing promises or observables directly by placing your assertion inside the then or subscribe block, invoke the done callback at the end of this block.

Compared to fakeAsync, using the done callback has the same advantages and disadvantages as waitForAsync.