Testing - Part 6 async( ) and fakeAsync( )
Overview
Time: 5min
In this module, we will learn to use manage async code in our tests with Angulars async and fakeAsync utilities
Learning Outcomes
- How to make a new async and fakeAsync
- How and when to use
.async
and.whenStable()
- How to use
.fakeAsync()
andtick()
Add a fakeAsync test to check the next joke button is working
Time: 15min
The fakeAsync()
function is one of the Angular testing utilities along with async() which we will discuss next. It simplifies coding of asynchronous tests by arranging for the tester's code to run in a special async test zone. The fakeAsync function enables a linear coding style by running the test body in a special fakeAsync test zone. The main advantage of fakeAsync over async is that syntax makes the code look synchronous, and you have no promise to manage just use the .tick()
method. When you have to manage time for example if you need to have known if a UI element has disappeared after three seconds you can fast forward to time by using tick(3000)
.
The main limitations of fakeAsync is you cannot make an XHR call from within a fakeAsync.
- Add a new test to check when the "Get next joke" button is clicked a new joke is rendered onto the page
- Wrap it block in a fakeAsync function.
it('should get next quote on click - with fakeAsync', fakeAsync(() => { });
- Add a spyOn function to spy on the same getJoke method in the joke service. However this time we will use
.returnValues()
with an s vs.getValue
which will return the next value specified in order of the calls being made - Add a timeout operator to the second value returned from the spy to simulate a longer running asynchronous operation
Observable.of('FAKE JOKE 2').timeout(2000)
. - Call
fixture.detectChanges()
to initialize the component and set the first joke - Set an expectation the first joke was set correctly
- Get a handle on the native element of the button and call
.click()
on it - Call
fixture.detectChanges()
again to update the UI with the second joke - Call
.tick(3000)
to move time forward three seconds to after the timeout set already. - Assert the text content in the components HTML Template is equal to the returned value from the spy
- Run the tests with
ng test
in the command line
src/app/joke/joke.component.spec.ts
it(`should get next quote on click - with fakeAsync`, fakeAsync(() => {
spyOn(jokeService, 'getJoke')
.and.returnValues(
Observable.of('FAKE JOKE'),
Observable.of('FAKE JOKE 2').timeout(2000));
fixture.detectChanges();
let el = de.query(By.css('p')).nativeElement;
expect(el.textContent).toEqual('FAKE JOKE');
let button = fixture.debugElement.query(By.css('button')).nativeElement;
button.click();
fixture.detectChanges();
tick(3000);
expect(el.textContent).toEqual('FAKE JOKE 2');
}));
Add an async test to check the next joke button is working
Time: 15min
The async function is one of the Angular testing utilities. It simplifies coding of asynchronous tests by arranging for the tester's code to run in a special async test zone.
Using async in a test can reduce verbose code blocks and avoid some of the issues discussed in the net step when using a done callback from Jasmine. However, sometimes it is necessary to call .fixture.whenStable()
in some async tests as asynchronous code continues to finish executing.
- Add a new test to check when the "Get next joke" button is clicked a new joke is rendered onto the page
- Wrap it block in an async function.
it('should get next quote on click - with async', async(() => { });
- Add a spyOn function to spy on the same getJoke method in the joke service. However, this time we will use
.returnValues()
with an s vs.getValue
which will return the next value specified in order of the calls being made - Call
fixture.detectChanges()
to initialize the component and set the first joke - Set an expectation the first joke was set correctly
- Get a handle on the native element of the button and call
.click()
on it - Call
fixture.detectChanges()
again to update the UI with the second joke - Call
fixture.whenStable().then
- Assert the text content in the components HTML Template is equal to the returned value from the spy in the code block of the whenStables
.then()
- Run the tests with
ng test
in the command line
src/app/joke/joke.component.spec.ts
it(`should get next quote on click - with async`, async(() => {
spyOn(jokeService, 'getJoke')
.and.returnValues(
Observable.of('FAKE JOKE'),
Observable.of('FAKE JOKE 2'));
fixture.detectChanges();
let el = de.query(By.css('p')).nativeElement;
expect(el.textContent).toEqual('FAKE JOKE');
let button = fixture.debugElement.query(By.css('button')).nativeElement;
button.click();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(el.textContent).toEqual('FAKE JOKE 2');
});
}));
Jasmine done( ) alternative method to async and fakeAsync
Time: 15min
While fakeAsync and even async function greatly simplifies Angular asynchronous testing, you can still fallback to the traditional Jasmine asynchronous testing technique.
You can still pass it a function that takes a done callback. Now you are responsible for chaining promises, handling errors, and calling done at the appropriate moment.
it('should get next quote on click - with done', (done) => {
let spy = spyOn(jokeService, 'getJoke')
.and.returnValues(
Observable.of('FAKE JOKE'),
Observable.of('FAKE JOKE 2'));
fixture.detectChanges();
let el = de.query(By.css('p')).nativeElement;
let button = fixture.debugElement.query(By.css('button')).nativeElement;
button.click();
spy.calls.mostRecent().returnValue
.subscribe(() => {
fixture.detectChanges();
expect(el.textContent).toEqual('FAKE JOKE 2');
done();
});
});
Although we have no direct access to the getJoke promise inside JokeComponent, the spy does and that makes it possible to wait for getJoke to finish.
The jasmine.done technique, while discouraged, may become necessary when neither async nor fakeAsync can tolerate a particular asynchronous activity. That's rare but it happens.
Final code
Time: 0min
src/app/joke/joke.component.spec.ts
import { JokeComponent } from './joke.component';
import { Observable } from 'rxjs/Observable';
import { TestBed, ComponentFixture, tick, async, fakeAsync } from '@angular/core/testing';
import { JokeService } from './joke.service';
import { DebugElement } from '@angular/core';
import { HttpModule } from '@angular/http';
import 'rxjs/Rx';
import { By } from '@angular/platform-browser';
describe(`Component: JokeComponent`, () => {
let component: JokeComponent;
let jokeService: JokeService;
let fixture: ComponentFixture<JokeComponent>;
let de: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
declarations: [JokeComponent],
providers: [
JokeService
]
});
fixture = TestBed.createComponent(JokeComponent);
component = fixture.componentInstance;
jokeService = TestBed.get(JokeService);
de = fixture.debugElement;
}));
it(`should add 1 + 1 `, () => {
expect(1 + 1).toEqual(2);
});
it(`should have a title of "Chuck Norris Quotes"`, () => {
expect(component.title).toEqual('Chuck Norris Jokes');
});
it(`should set the joke property when component initialized`, () => {
spyOn(jokeService, 'getJoke')
.and.returnValue(Observable.of('FAKE JOKE'));
fixture.detectChanges();
expect(component.joke).toEqual('FAKE JOKE');
});
it(`should have the joke content bound to the the page`, () => {
spyOn(jokeService, 'getJoke')
.and.returnValue(Observable.of('FAKE JOKE'));
fixture.detectChanges();
let el = de.query(By.css('p')).nativeElement;
expect(el.textContent).toEqual('FAKE JOKE');
});
it(`should get next quote on click - with fakeAsync`, fakeAsync(() => {
spyOn(jokeService, 'getJoke')
.and.returnValues(
Observable.of('FAKE JOKE'),
Observable.of('FAKE JOKE 2').timeout(2000));
fixture.detectChanges();
let el = de.query(By.css('p')).nativeElement;
expect(el.textContent).toEqual('FAKE JOKE');
let button = fixture.debugElement.query(By.css('button')).nativeElement;
button.click();
fixture.detectChanges();
tick(3000);
expect(el.textContent).toEqual('FAKE JOKE 2');
}));
it(`should get next quote on click - with async`, async(() => {
spyOn(jokeService, 'getJoke')
.and.returnValues(
Observable.of('FAKE JOKE'),
Observable.of('FAKE JOKE 2'));
fixture.detectChanges();
let el = de.query(By.css('p')).nativeElement;
expect(el.textContent).toEqual('FAKE JOKE');
let button = fixture.debugElement.query(By.css('button')).nativeElement;
button.click();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(el.textContent).toEqual('FAKE JOKE 2');
});
}));
it('should get next quote on click - with done', (done) => {
let spy = spyOn(jokeService, 'getJoke')
.and.returnValues(
Observable.of('FAKE JOKE'),
Observable.of('FAKE JOKE 2'));
fixture.detectChanges();
let el = de.query(By.css('p')).nativeElement;
let button = fixture.debugElement.query(By.css('button')).nativeElement;
button.click();
spy.calls.mostRecent().returnValue
.subscribe(() => {
fixture.detectChanges();
expect(el.textContent).toEqual('FAKE JOKE 2');
done();
});
});
});
Summary
Time: 5min
In this lesson, we looked at how to create an async
and fakeAsync
utilities to help manage asynchronous code in our unit tests