Angular 2 Unit Testing
October 25, 2016
Vikram Subramanian
Google
Introduction
● Angular 2.0 final!
● Changes in Test API from RC to final
○ Decrease number of concepts and boilerplate
○ Independent of test framework
○ NgModule
Introduction
Pragmatic Unit testing
● Decisions to make
● Testing Recipes
Decisions
Should I write unit tests?
○ Yes
○ Yes!!
Framework to use
○ Jasmine
○ Mocha
Test Runner
○ Karma
Decisions - Angular specific
● Testing Module Setup
○ How much to mock?
○ Isolation vs Being close to production
● Test Method Setup
○ () => { }
○ async() => { }
○ fakeAsync() => { }
Decisions - Angular specific (contd.)
● Locating elements
○ Using DOM API
○ Using DebugElement
● Dispatching events
○ Using DOM API
○ Using DebugElement
Recipes
Simple Component with templateUrl
Recipes - Simple Component with templateUrl
[app.component.ts]
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: 'app.component.html'
})
export class AppComponent {}
[app.component.html]
<h1>My First Angular App</h1>
Recipes - Simple Component with templateUrl
[app.component.spec.ts]
...
beforeEach(async(() => {
TestBed.configureTestingModule({imports: [AppModule]});
// Precompile components with templateUrl.
TestBed.compileComponents();
}));
...
Recipes - Simple Component with templateUrl
[app.component.spec.ts]
...
// Synchronous test method.
it('displays properly', () => {
let fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe("My First Angular App");
});
...
Recipes
Component communicating with backend service
Component
Users Service
Backend
Angular App
Recipes - Component Communicating with Backend service
beforeEach(async(() => {
TestBed.configureTestingModule({imports: [AppModule]});
// Override providers for the UsersService in the App module.
TestBed.overrideModule(AppModule,
{set:
{providers: [{provide: UsersService, useClass: MockUsersService}]}
}
);
TestBed.compileComponents();
}));
Recipes - Component Communicating with Backend service
it('displays user details on click', async(() => {
...
// Locate the fetch button.
let debugFetchButton = fixture.debugElement.query(By.css('button'));
expect(debugFetchButton).not.toBe(null);
// Trigger the click event through the DOM.
debugFetchButton.nativeElement.click();
...
}
Recipes - Component Communicating with Backend service
it('displays users list on click', async(() => {
...
// Wait for the async getUsers to complete and Angular to become stable.
fixture.whenStable().then(() => {
// Trigger rendering component state to DOM.
fixture.detectChanges();
// Check that the user list is displayed.
...
}
}
Recipes - Component Communicating with Backend service
// fakeAsync() version.
it('displays user details on click(fakeAsync)', fakeAsync(() => {
...
// Trigger the click event through the DOM.
debugFetchButton.nativeElement.click();
// Wait for Promise resolution and Angular to stabilize.
tick();
fixture.detectChanges();
...
}
Recipes - Component Communicating with Backend service
import {XHRBackend} from '@angular/http';
import {MockBackend} from '@angular/http/testing';
...
// Setup for mocking the HTTP Backend.
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [
UsersService,
{ provide: XHRBackend, useClass: MockBackend }
]
...
Recipes - Component Communicating with Backend service
it('returns all users', async(() => {
let backend = TestBed.get(XHRBackend);
let http = TestBed.get(Http);
let service = new UsersService(http);
let fakeUsers = makeUsers();
let options = new ResponseOptions({status: 200, body: fakeUsers});
let response = new Response(options);
backend.connections.subscribe(
(c: MockConnection) => c.mockRespond(response));
service.getUsers().then(users => { ...
Recipes
Testing Application Routing
Recipes - Testing Application Routing
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes(ROUTES),
AppModule
]
});
Recipes - Testing Application Routing
// async version
router.navigateByUrl('/about');
fixture.whenStable().then(() => {
fixture.detectChanges();
// Verify we navigated to About page.
let desc = fixture.debugElement.query(By.css('.description'));
expect(desc).not.toBe(null);
expect(desc.nativeElement.textContent).toContain('All about this sample');
});
Recipes - Testing Application Routing
// fakeAsync Version
router.navigateByUrl('/about');
tick();
fixture.detectChanges();
// Verify we navigated to About page.
let desc = fixture.debugElement.query(By.css('.description'));
expect(desc).not.toBe(null);
expect(desc.nativeElement.textContent).toContain('All about this sample');
Recipes
Testing Nested Components
Recipes - Testing Nested Components
[app.component.html]
<app-banner></app-banner>
<app-welcome></app-welcome>
<user-details></user-details>
Testing nested components - Approach 1
Don’t mock out anything
beforeEach(async(() => {
TestBed.configureTestingModule({imports: [AppModule]});
TestBed.compileComponents();
}));
Testing nested components - Approach 2
Mock all dependencies
...
TestBed.configureTestingModule({
declarations: [AppComponent, MockBannerComponent,
MockWelcomeComponent, MockUserDetailsComponent],
});
...
Testing nested components - Approach 3
Shallow Testing - NO_ERRORS_SCHEMA
...
TestBed.configureTestingModule({
declarations: [AppComponent],
schemas: [NO_ERRORS_SCHEMA],
});
...
WRITE UNIT TESTS!!
Resources
● Angular.io Testing Guide
● Testing Angular 2 - Julie Ralph
● Three ways to test Angular 2 components
Acknowledgements
● Mashhood Rastgar, Gerard Sans - Ideas for the talk
● Jasmine Plunker template - Ken Rimple - @krimple
● Ward and the docs team for putting up the best docs
Backup
Properties of good unit tests
● Fast
● Isolated
● Repeatable
● Self-verifying
● Timely
Source: https://pragprog.com/magazines/2012-01/unit-tests-are-first

Angular 2 Unit Testing.pptx

  • 1.
    Angular 2 UnitTesting October 25, 2016 Vikram Subramanian Google
  • 2.
    Introduction ● Angular 2.0final! ● Changes in Test API from RC to final ○ Decrease number of concepts and boilerplate ○ Independent of test framework ○ NgModule
  • 3.
    Introduction Pragmatic Unit testing ●Decisions to make ● Testing Recipes
  • 4.
    Decisions Should I writeunit tests? ○ Yes ○ Yes!! Framework to use ○ Jasmine ○ Mocha Test Runner ○ Karma
  • 5.
    Decisions - Angularspecific ● Testing Module Setup ○ How much to mock? ○ Isolation vs Being close to production ● Test Method Setup ○ () => { } ○ async() => { } ○ fakeAsync() => { }
  • 6.
    Decisions - Angularspecific (contd.) ● Locating elements ○ Using DOM API ○ Using DebugElement ● Dispatching events ○ Using DOM API ○ Using DebugElement
  • 7.
  • 8.
    Recipes - SimpleComponent with templateUrl [app.component.ts] @Component({ moduleId: module.id, selector: 'my-app', templateUrl: 'app.component.html' }) export class AppComponent {} [app.component.html] <h1>My First Angular App</h1>
  • 9.
    Recipes - SimpleComponent with templateUrl [app.component.spec.ts] ... beforeEach(async(() => { TestBed.configureTestingModule({imports: [AppModule]}); // Precompile components with templateUrl. TestBed.compileComponents(); })); ...
  • 10.
    Recipes - SimpleComponent with templateUrl [app.component.spec.ts] ... // Synchronous test method. it('displays properly', () => { let fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); expect(fixture.nativeElement.textContent).toBe("My First Angular App"); }); ...
  • 11.
  • 12.
  • 13.
    Recipes - ComponentCommunicating with Backend service beforeEach(async(() => { TestBed.configureTestingModule({imports: [AppModule]}); // Override providers for the UsersService in the App module. TestBed.overrideModule(AppModule, {set: {providers: [{provide: UsersService, useClass: MockUsersService}]} } ); TestBed.compileComponents(); }));
  • 14.
    Recipes - ComponentCommunicating with Backend service it('displays user details on click', async(() => { ... // Locate the fetch button. let debugFetchButton = fixture.debugElement.query(By.css('button')); expect(debugFetchButton).not.toBe(null); // Trigger the click event through the DOM. debugFetchButton.nativeElement.click(); ... }
  • 15.
    Recipes - ComponentCommunicating with Backend service it('displays users list on click', async(() => { ... // Wait for the async getUsers to complete and Angular to become stable. fixture.whenStable().then(() => { // Trigger rendering component state to DOM. fixture.detectChanges(); // Check that the user list is displayed. ... } }
  • 16.
    Recipes - ComponentCommunicating with Backend service // fakeAsync() version. it('displays user details on click(fakeAsync)', fakeAsync(() => { ... // Trigger the click event through the DOM. debugFetchButton.nativeElement.click(); // Wait for Promise resolution and Angular to stabilize. tick(); fixture.detectChanges(); ... }
  • 17.
    Recipes - ComponentCommunicating with Backend service import {XHRBackend} from '@angular/http'; import {MockBackend} from '@angular/http/testing'; ... // Setup for mocking the HTTP Backend. beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpModule], providers: [ UsersService, { provide: XHRBackend, useClass: MockBackend } ] ...
  • 18.
    Recipes - ComponentCommunicating with Backend service it('returns all users', async(() => { let backend = TestBed.get(XHRBackend); let http = TestBed.get(Http); let service = new UsersService(http); let fakeUsers = makeUsers(); let options = new ResponseOptions({status: 200, body: fakeUsers}); let response = new Response(options); backend.connections.subscribe( (c: MockConnection) => c.mockRespond(response)); service.getUsers().then(users => { ...
  • 19.
  • 20.
    Recipes - TestingApplication Routing TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes(ROUTES), AppModule ] });
  • 21.
    Recipes - TestingApplication Routing // async version router.navigateByUrl('/about'); fixture.whenStable().then(() => { fixture.detectChanges(); // Verify we navigated to About page. let desc = fixture.debugElement.query(By.css('.description')); expect(desc).not.toBe(null); expect(desc.nativeElement.textContent).toContain('All about this sample'); });
  • 22.
    Recipes - TestingApplication Routing // fakeAsync Version router.navigateByUrl('/about'); tick(); fixture.detectChanges(); // Verify we navigated to About page. let desc = fixture.debugElement.query(By.css('.description')); expect(desc).not.toBe(null); expect(desc.nativeElement.textContent).toContain('All about this sample');
  • 23.
  • 24.
    Recipes - TestingNested Components [app.component.html] <app-banner></app-banner> <app-welcome></app-welcome> <user-details></user-details>
  • 25.
    Testing nested components- Approach 1 Don’t mock out anything beforeEach(async(() => { TestBed.configureTestingModule({imports: [AppModule]}); TestBed.compileComponents(); }));
  • 26.
    Testing nested components- Approach 2 Mock all dependencies ... TestBed.configureTestingModule({ declarations: [AppComponent, MockBannerComponent, MockWelcomeComponent, MockUserDetailsComponent], }); ...
  • 27.
    Testing nested components- Approach 3 Shallow Testing - NO_ERRORS_SCHEMA ... TestBed.configureTestingModule({ declarations: [AppComponent], schemas: [NO_ERRORS_SCHEMA], }); ...
  • 28.
  • 29.
    Resources ● Angular.io TestingGuide ● Testing Angular 2 - Julie Ralph ● Three ways to test Angular 2 components
  • 30.
    Acknowledgements ● Mashhood Rastgar,Gerard Sans - Ideas for the talk ● Jasmine Plunker template - Ken Rimple - @krimple ● Ward and the docs team for putting up the best docs
  • 32.
    Backup Properties of goodunit tests ● Fast ● Isolated ● Repeatable ● Self-verifying ● Timely Source: https://pragprog.com/magazines/2012-01/unit-tests-are-first

Editor's Notes

  • #4 Decisions to take while unit testing and some recipes for some common situations that arise during Angular development
  • #5 High level decisions that you take once per project
  • #6 Decisions to take per module/component that are more Angular specific
  • #7 Prefer DOM API - If Component is tied to DOM Renderer anyway DebugElement - Renderer independent way and also query by Directive type
  • #8 http://embed.plnkr.co/aTS4NZjeUIpIGTqZwEl9/
  • #11 Asynchronous is contained in the test setup in beforeEach. The actual test method itself can be synchronous
  • #14 Reuse NgModule from the application and use overrideModule to override any of the module settings
  • #15 Locate and trigger events on component elements
  • #16 fixture.whenStable - Wait for Angular to stabilize. Similar to what Protractor does in E2E tests
  • #17 Same test written using fakeAsync. fakeAsync lets you control the async event queue in a synchronous manner through the magic of Zone.js.