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: