Testing Asynchronous Code in Angular Using FakeAsync

Netanel Basal
Netanel Basal
Published in
7 min readOct 16, 2019

--

Zone.js monkey patches asynchronous APIs such as setTimeout, XHR, etc., and exposes lifecycle hooks such as onScheduleTask or onInvokeTask, that provide us with the ability to monitor and intercept when a task is registered or completed.

The main goal of this library is to let Angular know when it should run change detection and update the view in a transparent way for the user.

fakeAsync is a special zone that lets us test asynchronous code in a synchronous way. Unlike the original zone that performs some work and delegates the task to the browser or Node.js, fakeAsync buffers each task internally, exposes an API that lets us decide when the task should be executed.

In this article, we’ll learn how we can use it to make async tests predictable, and how it works under the hood. To make the code cleaner and easier to read, in the following examples, I’ll use Spectator. It’ll work in the same way as when using the native testing API directly.

Testing Promises

Let’s say we have a service that initiates an HTTP call to get a list of users:

In our component, we want to fetch the users by calling the getUsers() method, and display them in a list:

Now it’s time to test our component. Let’s create a new Spectator spec:

First, we set detectChanges to false so we can control when the ngOnInit hook runs. The reason we do so is that we can mock the getUsers() implementation before it runs, thus avoid creating an HTTP request and make our test stable.

Next, we add the UserService to the mocks array. This instructs Spectator to convert each provider’s method into a Jasmine spy automatically (i.e. jasmine.createSpy()).

Lastly, we create an it block and wrap it with the fakeAsync function. This causes our test to be executed in the fakeAsync zone. Let’s finish our test:

We grab a reference to the UserService and use callFake to return a Promise with a mock data that simulates an HTTP request. Then, we call detectChanges(), which executes the ngOnInit method.

At this point, we want to resolve the Promise, so we call flushMicrotasks(). The way it works is that the fakeAsync zone listens to the zone’s onScheduleTask hook. This hook is called before the async operation is scheduled and delegates to the browser.

One of the parameters we get from this hook is the task object. We can distinguish between the different task types by examining the type property, which can be microTask, macroTask, or eventTask.

If you’re not familiar with the difference between a microtask and a macrotask, I recommend reading this article.

Because a Promise is considered to be a microtask, we’ll reach the following code:

The code above does one simple thing: Whenever it’s notified on a new microtask, it adds it to an internal microtasks queue. Then when we call the flushMicrotasks() function, it invokes each one of the microtasks that are in the queue:

This way, we can synchronously control the microtasks’ execution time.

Testing Timers

Using fakeAsync, we can easily test timers based APIs such as setInterval, setTimeout, and setImmediate. Let’s see how we can use it to test each one of them:

Testing setTimeout

Let’s say that we have a component that needs to show a message when the user clicks a button and make it automatically disappear after two seconds:

Now, let’s write a spec to verify that our component works as expected:

We start by making two synchronous assertions. First, we verify that the message element doesn’t exist when the component is initialized. Then, we click the button and check that it exists.

The last assertion we need to make is that the message disappears after two seconds. In this case, because we work with timers, we need a method that allows us to control time. fakeAsync exposes the tick() function that gives us just that.

The tick() function can be used only inside a fakeAsync zone. It gives us the power to simulates the asynchronous passage of time using a virtual clock.

The tick() function takes as parameter the number of milliseconds we want to run forward. In our example, we use a setTimeout() of two seconds, so we need to call it with 2000 milliseconds. This advances the virtual clock by two seconds, resulting in the setTimeout() callback to be called.

Then, we call detectChanges() so that Angular will update the view based on the new component’s state, and make our assertion.

Let’s examine what’s happening under the hood. Because setTimeout is a macrotask, we’ll reach the following code block:

If we follow the code and get into the scheduleFunction method, we’ll see that as we saw earlier with the microtasks, it pushes the task that contains all the parameters it needs to an internal queue named schedulerQueue. It never calls the real implementation of the setTimeout function:

When we call tick(), it loops over each task in the queue, and for each one, based on the current time, it checks if it’s time to execute it:

That’s simple as that. Another thing to keep in mind is that when we call tick(), it first calls the flushMicrotasks() function, as microtasks should run before macrotasks.

That was a simple example. Let’s see a bit more complicated case. Let’s say we need to enable our users to search in a list:

The code is straightforward, so I’ll skip the explanation, and will jump straight into the test:

We start by simulating an HTTP request and returning mock data. Next, we type in the search input. At this point, we hit the debounceTime operator. To execute it, we need to advance the clock in 100 milliseconds. Then, we reach the first tap operator and assert that we display the loading element.

Lastly, we run our last timer by advancing the clock one more time in 100 milliseconds, which causes the userService.search() method to be executed. This results in the loading element to disappear and the list to be shown.

Testing setInterval

When we work with setInterval, the process is the same. We need to call the tick() method passing the number of milliseconds we want the virtual clock to move forward.

Let’s say we use setInterval to show the user a progress bar:

To test it, we need a way to control time, so we’ll use fakeAsync:

First, we check the initial component state. Then we advance the virtual clock two times and make our assertions. The calls to tick() are cumulative within a test, so each time we call it, it will increment the current time with the value that we passed.

Note that we can’t leave a test with pending timers. If we do so, we’ll get an error:

That’s why we can’t test code that performs polling, for example.

Testing requestAnimationFrame

Let’s create a dummy component that uses requestAnimationFrame and learn how we test it:

Inside the fakeAsync zone, requestAnimationFrame is handled as a macrotask. Under the hood, it actually simulates it by using a setTimeout with 16 ms, which are equivalent to 60 frames per second:

Based on this information, if we want to execute the requestAnimationFrame callback, we should advance the virtual clock by 16 ms:

You can find the complete source code here.

Summary

We’ve learned what a fakeAsync is and how it works under the hood. We learned that we could use the flushMicrotaks() function to run microtasks such as Promises or use tick() to control time and run timers based APIs.

Things we haven’t touched that fakeAsync supports are jasmine.clock(), RxJS schedulers, and JS Dates. To test them, you’ll use the same technique by working with the tick() function.

Additional Resources

🚀 In Case You Missed It

  • Akita: One of the leading state management libraries, used in countless production environments. Whether it’s entities arriving from the server or UI state data, Akita has custom-built stores, powerful tools, and tailor-made plugins, which all help to manage the data and negate the need for massive amounts of boilerplate code.
  • Spectator: A library that runs as an additional layer on top of the Angular testing framework, that saves you from writing a ton of boilerplate. V4 just came out!
  • And of course, Transloco: The Internationalization library Angular 😀

Follow me on Medium or Twitter to read more about Angular, Akita and JS!

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.