Async services

Very few services are nice and well-behaved, in the sense that they are synchronous. A lot of the time, your service will be asynchronous and the return from it is most likely an observable or a promise. If you are using RxJS with the Http service or HttpClient, it will be an observable, but if using the fetch API, it will be a promise. These are two good options for dealing with HTTP, but the Angular team added the RxJS library to Angular to make your life as a developer easier. Ultimately it's up to you, but we recommend going with RxJS.

Angular has two constructs ready to tackle the asynchronous scenario when testing:

  • async() and whenStable(): This code ensures that any promises are immediately resolved; it can look more synchronous though
  • fakeAsync() and tick(): This code does what the async does but it looks more synchronous when used

Let's describe the async() and whenStable() approaches. Our service has now grown up and is doing something asynchronous when we call it like a timeout or a HTTP call. Regardless of which, the answer doesn't reach us straightaway. By using async() in combination with whenStable(), we can, however, ensure that any promises are immediately resolved. Imagine our service now looks like this:

export class AsyncDependencyService {
getData(): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('data') }, 3000);
})
}
}

We need to change our spy setup to return a promise instead of returning a static string, like so:

spy = spyOn(dependency,'getData')
.and.returnValue(Promise.resolve('spy data'));

We do need to change inside of our component, like so:

import { Component, OnInit } from '@angular/core';
import { AsyncDependencyService } from "./async.dependency.service";

@Component({
selector: 'async-example',
template: `
<div>{{ title }}</div>
`
})
export class AsyncExampleComponent {
title: string;

constructor(private service: AsyncDependencyService) {
this.service.getData().then(data => this.title = data);
}
}

At this point, it's time to update our tests. We need to do two more things. We need to tell our test method to use the async() function, like so:

it('async test', async() => {
// the test body
})

We also need to call fixture.whenStable() to make sure that the promise will have had ample time to resolve, like so:

import { TestBed } from "@angular/core/testing";
import { AsyncExampleComponent } from "./async.example.component";
import { AsyncDependencyService } from "./async.dependency.service";

describe('test an component with an async service', () => {
let fixture;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [AsyncExampleComponent],
providers: [AsyncDependencyService]
});

fixture = TestBed.createComponent(AsyncExampleComponent);
});

it('should contain async data', async () => {
const component = fixture.componentInstance;
fixture.whenStable.then(() => {
fixture.detectChanges();
expect(component.title).toBe('async data');
});

});
});

This version of doing it works as it should, but feels a bit clunky. There is another approach using fakeAsync() and tick(). Essentially, fakeAsync() replaces the async() call and we get rid of whenStable(). The big benefit, however, is that we no longer need to place our assertion statements inside of the promise's then() callback. This gives us synchronous-looking code. Back to fakeAsync(), we need to make a call to tick(), which can only be called within a fakeAsync() call , like so:

it('async test', fakeAsync() => {
let component = fixture.componentInstance;
fixture.detectChanges();
fixture.tick();
expect(component.title).toBe('spy data');
});

As you can see, this looks a lot cleaner; which version you want to use for async testing is up to you.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset