digitaldrummerjhttps://digitaldrummerj.me
Justin James
Web Developer
Microsoft MVP
Professional Speaker
digitaldrummerjhttps://digitaldrummerj.me
Unit Tests Are Proof That My
Code Does What I Think It Does
2
digitaldrummerjhttps://digitaldrummerj.me
Release Faster
3
digitaldrummerjhttps://digitaldrummerj.me
Higher Confidence
4
digitaldrummerjhttps://digitaldrummerj.me
Refactor with Ease
5
digitaldrummerjhttps://digitaldrummerj.me
We only get advantages when we are comfortable writing good tests
Disclaimer
digitaldrummerjhttps://digitaldrummerj.me
Single
Assumption
Single
Unit
Automated
Tenents
7
digitaldrummerjhttps://digitaldrummerj.me
IntentPinpoint Bugs
RegressionFunctionality
Proof
8
digitaldrummerjhttps://digitaldrummerj.me
RunnableDependableMaintainable
Qualities
9
digitaldrummerjhttps://digitaldrummerj.me
Well NamedEasy To Write
Easy To ReadNot Brittle
Maintainable Qualities
10
digitaldrummerjhttps://digitaldrummerj.me
Tests Right ThingsContinued Relevance
IsolatedConsistent Results
Dependable Qualities
11
digitaldrummerjhttps://digitaldrummerj.me
Failures Point
to Problem
Repeatable
Fast
Runnable Qualities
Single Click
12
digitaldrummerjhttps://digitaldrummerj.me
End to End Tests
(~10%)
Unit Tests
(~70%)
Integration Tests
(~20%)
Testing Pyramid
digitaldrummerjhttps://digitaldrummerj.me
Unit Testing
Will Not Fix
Bad Practices
14
digitaldrummerjhttps://digitaldrummerj.me
Need Good Grasp Of
Angular Patterns
15
digitaldrummerjhttps://digitaldrummerj.me
GuardsPipesRoutes
ServicesComponentsModules
Angular Patterns
16
digitaldrummerjhttps://digitaldrummerj.me
http://nodejs.org
Node JS (8+)
npm install -g @angular/cli
NPM Global Packages
ng new AppName --style scss --routing
Create New Project
npm test
Unit Test
Angular CLI
Setup
17
digitaldrummerjhttps://digitaldrummerj.me
Test Runner Test Framework Testing Utilities
Karma
Tools
Jasmine Angular
18
digitaldrummerjhttps://digitaldrummerj.me 19
digitaldrummerjhttps://digitaldrummerj.me 20
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
describe('First spec', () => {
it('should pass', () => {
expect(true).toBeTruthy();
});
});
Simple Passing Test
expect(false).toBeTruthy();
Simple Failing Test
digitaldrummerjhttps://digitaldrummerj.me
Test Setup
beforeEach / afterEach
beforeAll / afterAll
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerjhttps://digitaldrummerj.me
TestBed
Configure Test Module
Get References
Trigger Change Detection
Access to DOM Elements
import { TestBed } from '@angular/core/testing';
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [],
declarations: [],
providers: [],
schemas: [NO_ERRORS_SCHEMA],
});
});
TestBed Config Layout
import { TestBed } from '@angular/core/testing';
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [],
declarations: [],
providers: [],
schemas: [NO_ERRORS_SCHEMA],
});
});
TestBed Config Layout
digitaldrummerjhttps://digitaldrummerj.me
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-simple',
templateUrl: './simple.component.html',
styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {
subject = 'World';
constructor() { }
ngOnInit() {
}
}
Component
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-simple',
templateUrl: './simple.component.html',
styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {
subject = 'World';
constructor() { }
ngOnInit() {
}
}
Component
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-simple',
templateUrl: './simple.component.html',
styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {
subject = 'World';
constructor() { }
ngOnInit() {
}
}
Component
digitaldrummerjhttps://digitaldrummerj.me
Configure Test Module
40
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
});
TestBed
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
});
TestBed
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
});
TestBed
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
});
TestBed
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
});
TestBed
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
});
TestBed
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
});
TestBed
digitaldrummerjhttps://digitaldrummerj.me
Configure Test Module
Create Fixture
48
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
let fixture: ComponentFixture<SimpleComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
});
});
Create Component
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
let fixture: ComponentFixture<SimpleComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
});
});
Create Component
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
let fixture: ComponentFixture<SimpleComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
});
});
Create Component
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
let fixture: ComponentFixture<SimpleComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
});
});
Create Component
digitaldrummerjhttps://digitaldrummerj.me
Configure Test Module
Create Fixture
Get Component Instance
53
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
let fixture: ComponentFixture<SimpleComponent>;
let component: SimpleComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
});
});
Create Component
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
let fixture: ComponentFixture<SimpleComponent>;
let component: SimpleComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
});
});
Create Component
it('should be created', () => {
expect(component).toBeTruthy();
});
Component Created Test
it('should be created', () => {
expect(component).toBeTruthy();
});
Component Created Test
digitaldrummerjhttps://digitaldrummerj.me
Configure Test Module
Create Fixture
Get Component Instance
Trigger Change Detection
58
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
let component: SimpleComponent;
let fixture: ComponentFixture<SimpleComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});
Create Component
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
describe('SimpleComponent', () => {
let component: SimpleComponent;
let fixture: ComponentFixture<SimpleComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SimpleComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
});
Create Component
digitaldrummerjhttps://digitaldrummerj.me
Configure Test Module
Create Fixture
Get Component Instance
Trigger Change Detection
Access Component's DOM
61
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
describe('SimpleComponent', () => {
let component: SimpleComponent;
let fixture: ComponentFixture<SimpleComponent>;
let element: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({ declarations: [ SimpleComponent ] })
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
element = fixture.debugElement;
fixture.detectChanges();
});
});
Element Debug
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
describe('SimpleComponent', () => {
let component: SimpleComponent;
let fixture: ComponentFixture<SimpleComponent>;
let element: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({ declarations: [ SimpleComponent ] })
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
element = fixture.debugElement;
fixture.detectChanges();
});
});
Element Debug
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
describe('SimpleComponent', () => {
let component: SimpleComponent;
let fixture: ComponentFixture<SimpleComponent>;
let element: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({ declarations: [ SimpleComponent ] })
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
element = fixture.debugElement;
fixture.detectChanges();
});
});
Element Debug
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
describe('SimpleComponent', () => {
let component: SimpleComponent;
let fixture: ComponentFixture<SimpleComponent>;
let element: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({ declarations: [ SimpleComponent ] })
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
element = fixture.debugElement;
fixture.detectChanges();
});
});
Element Debug
<h1>Hello {{ subject }}!</h1>
Component Html
it('greets the subject', () => {
const h1 = element.query(By.css('h1'));
expect(h1.nativeElement.innerText).toBe('Hello world!');
});
Test Element Value
it('greets the subject', () => {
const h1 = element.query(By.css('h1'));
expect(h1.nativeElement.innerText).toBe('Hello world!');
});
Test Element Value
it('greets the subject', () => {
const h1 = element.query(By.css('h1'));
expect(h1.nativeElement.innerText).toBe('Hello world!');
});
Test Element Value
digitaldrummerjhttps://digitaldrummerj.me
TestBed Recap
• Configures Module
• Create Component
• Accesses DOM
• Get injector references using TestBed.get
• Change detection not triggered by createdComponent
digitaldrummerjhttps://digitaldrummerj.me
import { Injectable } from '@angular/core';
@Injectable()
export class GreetingService {
subject = 'world';
constructor() { }
}
Simple Service
import { Injectable } from '@angular/core';
@Injectable()
export class GreetingService {
subject = 'world';
constructor() { }
}
Simple Service
import { Injectable } from '@angular/core';
@Injectable()
export class GreetingService {
subject = 'world';
constructor() { }
}
Simple Service
import { Component, OnInit } from '@angular/core';
import { GreetingService } from '../shared/services/greeting.service';
@Component({
selector: 'app-simple',
templateUrl: './simple.component.html',
styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {
subject = this.service.subject;
constructor(private service: GreetingService) { }
ngOnInit() { }
}
Component with Service
import { Component, OnInit } from '@angular/core';
import { GreetingService } from '../shared/services/greeting.service';
@Component({
selector: 'app-simple',
templateUrl: './simple.component.html',
styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {
subject = this.service.subject;
constructor(private service: GreetingService) { }
ngOnInit() { }
}
Component with Service
import { Component, OnInit } from '@angular/core';
import { GreetingService } from '../shared/services/greeting.service';
@Component({
selector: 'app-simple',
templateUrl: './simple.component.html',
styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {
subject = this.service.subject;
constructor(private service: GreetingService) { }
ngOnInit() { }
}
Component with Service
import { Component, OnInit } from '@angular/core';
import { GreetingService } from '../shared/services/greeting.service';
@Component({
selector: 'app-simple',
templateUrl: './simple.component.html',
styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {
subject = this.service.subject;
constructor(private service: GreetingService) { }
ngOnInit() { }
}
Component with Service
digitaldrummerjhttps://digitaldrummerj.me
Don't Use Real Services
79
digitaldrummerjhttps://digitaldrummerj.me
Don't Use Real Services
Use a Stub
80
digitaldrummerjhttps://digitaldrummerj.me
Don't Use Real Services
Use a Stub
Use a Spy
81
digitaldrummerjhttps://digitaldrummerj.me
Service Stub
82
export class GreetingServiceStub {
public subject = 'Test World';
}
Greeting Service Stub 8
describe('SimpleComponentWithServiceComponent', () => {
let component: SimpleComponentWithServiceComponent;
let fixture: ComponentFixture<SimpleComponentWithServiceComponent>;
let element: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SimpleComponentWithServiceComponent],
providers: [{ provide: GreetingService, useClass: GreetingServiceStub }]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponentWithServiceComponent);
component = fixture.componentInstance;
element = fixture.debugElement;
fixture.detectChanges();
});
});
Component With Service Setup
describe('SimpleComponentWithServiceComponent', () => {
let component: SimpleComponentWithServiceComponent;
let fixture: ComponentFixture<SimpleComponentWithServiceComponent>;
let element: DebugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SimpleComponentWithServiceComponent],
providers: [{ provide: GreetingService, useClass: GreetingServiceStub }]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponentWithServiceComponent);
component = fixture.componentInstance;
element = fixture.debugElement;
fixture.detectChanges();
});
});
Component With Service Setup
it('greets the subject', () => {
const h1 = element.query(By.css('h1'));
expect(h1.nativeElement.innerText).toBe('Hello Test World!');
});
Same Greet Test but With Service
it('greets the subject', () => {
const h1 = element.query(By.css('h1'));
expect(h1.nativeElement.innerText).toBe('Hello Test World!');
});
Same Greet Test but With Service
digitaldrummerjhttps://digitaldrummerj.me
Service Spying
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class GreetingService {
constructor() { }
getGreeting(): Observable<string> { return Observable.of('Hello'); }
getSubject(): Observable<string> { return Observable.of('World'); }
getPunctuation(): Observable<string> { return Observable.of('!'); }
}
Service
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class GreetingService {
constructor() { }
getGreeting(): Observable<string> { return Observable.of('Hello'); }
getSubject(): Observable<string> { return Observable.of('World'); }
getPunctuation(): Observable<string> { return Observable.of('!'); }
}
Service
export class SimpleComponentWithServiceComponent implements OnInit {
subject: string;
greeting: string;
punctuation: string;
constructor(private greetingService: GreetingService) { }
ngOnInit() {
this.greetingService.getSubject().subscribe((result) => {
this.subject = result;
});
this.greetingService.getGreeting().subscribe((result) => {
this.greeting = result;
});
this.greetingService.getPunctuation().subscribe((result) => {
this.punctuation = result;
});
}
}
Component
export class SimpleComponentWithServiceComponent implements OnInit {
subject: string;
greeting: string;
punctuation: string;
constructor(private greetingService: GreetingService) { }
ngOnInit() {
this.greetingService.getSubject().subscribe((result) => {
this.subject = result;
});
this.greetingService.getGreeting().subscribe((result) => {
this.greeting = result;
});
this.greetingService.getPunctuation().subscribe((result) => {
this.punctuation = result;
});
}
}
Component
export class SimpleComponentWithServiceComponent implements OnInit {
subject: string;
greeting: string;
punctuation: string;
constructor(private greetingService: GreetingService) { }
ngOnInit() {
this.greetingService.getSubject().subscribe((result) => {
this.subject = result;
});
this.greetingService.getGreeting().subscribe((result) => {
this.greeting = result;
});
this.greetingService.getPunctuation().subscribe((result) => {
this.punctuation = result;
});
}
}
Component
let component: SimpleComponentWithServiceComponent;
let fixture: ComponentFixture<SimpleComponentWithServiceComponent>;
let element: DebugElement;
let greetingService: GreetingService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SimpleComponentWithServiceComponent],
providers: [GreetingService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponentWithServiceComponent);
comonent = fixture.componentInstance;
element = fixture.debugElement;
greetingService = element.injector.get(GreetingService);
});
Setup
let component: SimpleComponentWithServiceComponent;
let fixture: ComponentFixture<SimpleComponentWithServiceComponent>;
let element: DebugElement;
let greetingService: GreetingService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SimpleComponentWithServiceComponent],
providers: [GreetingService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponentWithServiceComponent);
comonent = fixture.componentInstance;
element = fixture.debugElement;
greetingService = element.injector.get(GreetingService);
});
Setup
let component: SimpleComponentWithServiceComponent;
let fixture: ComponentFixture<SimpleComponentWithServiceComponent>;
let element: DebugElement;
let greetingService: GreetingService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SimpleComponentWithServiceComponent],
providers: [GreetingService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponentWithServiceComponent);
comonent = fixture.componentInstance;
element = fixture.debugElement;
greetingService = element.injector.get(GreetingService);
});
Setup
let component: SimpleComponentWithServiceComponent;
let fixture: ComponentFixture<SimpleComponentWithServiceComponent>;
let element: DebugElement;
let greetingService: GreetingService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SimpleComponentWithServiceComponent],
providers: [GreetingService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponentWithServiceComponent);
comonent = fixture.componentInstance;
element = fixture.debugElement;
greetingService = element.injector.get(GreetingService);
});
Setup
it('spying on greets', async(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.greeting).toBe('hello');
});
}));
Component Test with SpyOn
it('spying on greets', async(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.greeting).toBe('hello');
});
}));
Component Test with SpyOn
it('spying on greets', async(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.greeting).toBe('hello');
});
}));
Component Test with SpyOn
it('spying on greets', async(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.greeting).toBe('hello');
});
}));
Component Test with SpyOn
it('spying on greets', async(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.greeting).toBe('hello');
});
}));
Component Test with SpyOn
it('spying on greets', async(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.greeting).toBe('hello');
});
}));
Component Test with SpyOn
it('spying on greets', async(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.greeting).toBe('hello');
});
}));
Component Test with SpyOn
digitaldrummerjhttps://digitaldrummerj.me
Make Test Look More
Synchronous Code
import { tick, fakeAsync } from '@angular/core/testing';
it('fakeasync test', fakeAsync(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(component.greeting).toBe('hello');
}));
Test Using FakeAsync
import { tick, fakeAsync } from '@angular/core/testing';
it('fakeasync test', fakeAsync(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(component.greeting).toBe('hello');
}));
Test Using FakeAsync
import { tick, fakeAsync } from '@angular/core/testing';
it('fakeasync test', fakeAsync(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(component.greeting).toBe('hello');
}));
Test Using FakeAsync
import { tick, fakeAsync } from '@angular/core/testing';
it('fakeasync test', fakeAsync(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(component.greeting).toBe('hello');
}));
Test Using FakeAsync
import { tick, fakeAsync } from '@angular/core/testing';
it('fakeasync test', fakeAsync(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(component.greeting).toBe('hello');
}));
Test Using FakeAsync
import { tick, fakeAsync } from '@angular/core/testing';
it('fakeasync test', fakeAsync(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(component.gretting).toBe('hello');
}));
Test Using FakeAsync
import { tick, fakeAsync } from '@angular/core/testing';
it('fakeasync test', fakeAsync(() => {
spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello'));
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(component.greeting).toBe('hello');
}));
Test Using FakeAsync
digitaldrummerjhttps://digitaldrummerj.me
Component with Service Recap
• Use Spy or Stubs
• If spy, make sure to spy on everything in ngOnInit
• Make async code look more sync like with fakeAsync
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerjhttps://digitaldrummerj.me
Reactive Forms Tests
Validation Errors
Field Validity
Form Validity
Error Messages
Error Message Display
11
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {debounceTime} from 'rxjs/operators';
export class TodoComponent implements OnInit {
addForm: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.addForm = this.formBuilder.group({
'item': ['', [Validators.required, Validators.minLength(3)]]
});
this.addForm.valueChanges
.pipe(debounceTime(1000))
.subscribe(data =>
this.onValueChanged(data)
);
}
}
Reactive Form Setup
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {debounceTime} from 'rxjs/operators';
export class TodoComponent implements OnInit {
addForm: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.addForm = this.formBuilder.group({
'item': ['', [Validators.required, Validators.minLength(3)]]
});
this.addForm.valueChanges
.pipe(debounceTime(1000))
.subscribe(data =>
this.onValueChanged(data)
);
}
}
Reactive Form Setup
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {debounceTime} from 'rxjs/operators';
export class TodoComponent implements OnInit {
addForm: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.addForm = this.formBuilder.group({
'item': ['', [Validators.required, Validators.minLength(3)]]
});
this.addForm.valueChanges
.pipe(debounceTime(1000))
.subscribe(data =>
this.onValueChanged(data)
);
}
}
Reactive Form Setup
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {debounceTime} from 'rxjs/operators';
export class TodoComponent implements OnInit {
addForm: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.addForm = this.formBuilder.group({
'item': ['', [Validators.required, Validators.minLength(3)]]
});
this.addForm.valueChanges
.pipe(debounceTime(1000))
.subscribe(data =>
this.onValueChanged(data)
);
}
}
Reactive Form Setup
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {debounceTime} from 'rxjs/operators';
export class TodoComponent implements OnInit {
addForm: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.addForm = this.formBuilder.group({
'item': ['', [Validators.required, Validators.minLength(3)]]
});
this.addForm.valueChanges
.pipe(debounceTime(1000))
.subscribe(data =>
this.onValueChanged(data)
);
}
}
Reactive Form Setup
import { ReactiveFormsModule } from '@angular/forms';
import { AbstractControl, Validators } from '@angular/forms';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
declarations: [ TodoComponent ],
providers: [ { provide: TodoService, useClass: MockTodoService } ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TodoComponent);
element = fixture.debugElement;
component = element.componentInstance;
itemField = component.addForm.controls['item'];
fixture.detectChanges();
});
TestBed Setup
import { ReactiveFormsModule } from '@angular/forms';
import { AbstractControl, Validators } from '@angular/forms';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
declarations: [ TodoComponent ],
providers: [ { provide: TodoService, useClass: MockTodoService } ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TodoComponent);
element = fixture.debugElement;
component = element.componentInstance;
itemField = component.addForm.controls['item'];
fixture.detectChanges();
});
TestBed Setup
import { ReactiveFormsModule } from '@angular/forms';
import { AbstractControl, Validators } from '@angular/forms';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
declarations: [ TodoComponent ],
providers: [ { provide: TodoService, useClass: MockTodoService } ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TodoComponent);
element = fixture.debugElement;
component = element.componentInstance;
itemField = component.addForm.controls['item'];
fixture.detectChanges();
});
TestBed Setup
import { ReactiveFormsModule } from '@angular/forms';
import { AbstractControl, Validators } from '@angular/forms';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
declarations: [ TodoComponent ],
providers: [ { provide: TodoService, useClass: MockTodoService } ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TodoComponent);
element = fixture.debugElement;
component = element.componentInstance;
itemField = component.addForm.controls['item'];
fixture.detectChanges();
});
TestBed Setup
it('item field required with blank validity', () => {
itemField.setValue('');
errors = itemField.errors || {};
expect(errors['required']).toBeDefined('required validator not triggered');
expect(errors['minlength']).toBeUndefined('min length validator was triggered');
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Test Field Validity
it('item field required with blank validity', () => {
itemField.setValue('');
errors = itemField.errors || {};
expect(errors['required']).toBeDefined('required validator not triggered');
expect(errors['minlength']).toBeUndefined('min length validator was triggered');
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Test Field Validity
it('item field required with blank validity', () => {
itemField.setValue('');
errors = itemField.errors || {};
expect(errors['required']).toBeDefined('required validator not triggered');
expect(errors['minlength']).toBeUndefined('min length validator was triggered');
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Test Field Validity
it('item field required with blank validity', () => {
itemField.setValue('');
errors = itemField.errors || {};
expect(errors['required']).toBeDefined('required validator not triggered');
expect(errors['minlength']).toBeUndefined('min length validator was triggered');
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Test Field Validity
it('item field required with blank validity', () => {
itemField.setValue('');
errors = itemField.errors || {};
expect(errors['required']).toBeDefined('required validator not triggered');
expect(errors['minlength']).toBeUndefined('min length validator was triggered');
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Test Field Validity
it('item field min length too short validity', () => {
itemField.setValue('1');
errors = itemField.errors || {};
expect(errors['minlength']).toBeDefined();
expect(errors['required']).toBeUndefined();
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Min Length Validator
it('item field min length too short validity', () => {
itemField.setValue('1');
errors = itemField.errors || {};
expect(errors['minlength']).toBeDefined();
expect(errors['required']).toBeUndefined();
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Min Length Validator
it('item field min length too short validity', () => {
itemField.setValue('1');
errors = itemField.errors || {};
expect(errors['minlength']).toBeDefined();
expect(errors['required']).toBeUndefined();
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Min Length Validator
it('item field min length too short validity', () => {
itemField.setValue('1');
errors = itemField.errors || {};
expect(errors['minlength']).toBeDefined();
expect(errors['required']).toBeUndefined();
expect(itemField.valid).toBeFalsy();
expect(component.addForm.valid).toBeFalsy();
});
Min Length Validator
digitaldrummerjhttps://digitaldrummerj.me 1
digitaldrummerjhttps://digitaldrummerj.me
Field Not Set as Dirty
When Calling setvalue
13
beforeEach(() => {
itemField.markAsDirty();
expect(itemField.dirty).toBeTruthy('field should be dirty');
fixture.detectChanges();
});
Mark Field as Dirty
beforeEach(() => {
itemField.markAsDirty();
expect(itemField.dirty).toBeTruthy('field should be dirty');
fixture.detectChanges();
});
Mark Field as Dirty
beforeEach(() => {
itemField.markAsDirty();
expect(itemField.dirty).toBeTruthy('field should be dirty');
fixture.detectChanges();
});
Mark Field as Dirty
beforeEach(() => {
itemField.markAsDirty();
expect(itemField.dirty).toBeTruthy('field should be dirty');
fixture.detectChanges();
});
Mark Field as Dirty
digitaldrummerjhttps://digitaldrummerj.me
Dealing with Debounce Time
14
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {debounceTime} from 'rxjs/operators';
export class TodoComponent implements OnInit {
addForm: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.addForm = this.formBuilder.group({
'item': ['', [Validators.required, Validators.minLength(3)]]
});
this.addForm.valueChanges
.pipe(debounceTime(1000))
.subscribe(data =>
this.onValueChanged(data)
);
}
}
Reactive Form Setup
it('item field min length passes validity', fakeAsync(() => {
itemField.setValue('123');
tick(1000);
fixture.detectChanges();
expect(itemField.valid).toBeTruthy();
}));
Proper Debounce Testing
it('item field min length passes validity', fakeAsync(() => {
itemField.setValue('123');
tick(1000);
fixture.detectChanges();
expect(itemField.valid).toBeTruthy();
}));
Proper Debounce Testing
it('item field min length passes validity', fakeAsync(() => {
itemField.setValue('123');
tick(1000);
fixture.detectChanges();
expect(itemField.valid).toBeTruthy();
}));
Proper Debounce Testing
it('item field min length passes validity', fakeAsync(() => {
itemField.setValue('123');
tick(1000);
fixture.detectChanges();
expect(itemField.valid).toBeTruthy();
}));
Proper Debounce Testing
it('item field min length passes validity', fakeAsync(() => {
itemField.setValue('123');
tick(1000);
fixture.detectChanges();
expect(itemField.valid).toBeTruthy();
}));
Proper Debounce Testing
digitaldrummerjhttps://digitaldrummerj.me
Reactive Form Recap
• Test validation in component
• Dirty flag not set when calling setValue
• Make debounce work using tick(time)
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerjhttps://digitaldrummerj.me
Don't Call Actual Backend
digitaldrummerjhttps://digitaldrummerj.me
Use
HttpClientTestingModule
HttpTestingController
15
import {
HttpClientTestingModule,
HttpTestingController
}
from '@angular/common/http/testing';
describe('TodoService', () => {
let service: TodoService;
let httpTestingController: HttpTestingController;
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [TodoService],
});
service = bed.get(TodoService);
httpTestingController = TestBed.get(HttpTestingController);
});
});
HttpClientTestingModule and HttpTestingController
import {
HttpClientTestingModule,
HttpTestingController
}
from '@angular/common/http/testing';
describe('TodoService', () => {
let service: TodoService;
let httpTestingController: HttpTestingController;
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [TodoService],
});
service = bed.get(TodoService);
httpTestingController = TestBed.get(HttpTestingController);
});
});
HttpClientTestingModule and HttpTestingController
import {
HttpClientTestingModule,
HttpTestingController
}
from '@angular/common/http/testing';
describe('TodoService', () => {
let service: TodoService;
let httpTestingController: HttpTestingController;
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [TodoService],
});
service = bed.get(TodoService);
httpTestingController = TestBed.get(HttpTestingController);
});
});
HttpClientTestingModule and HttpTestingController
import {
HttpClientTestingModule,
HttpTestingController
}
from '@angular/common/http/testing';
describe('TodoService', () => {
let service: TodoService;
let httpTestingController: HttpTestingController;
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [TodoService],
});
service = bed.get(TodoService);
httpTestingController = TestBed.get(HttpTestingController);
});
});
HttpClientTestingModule and HttpTestingController
import {
HttpClientTestingModule,
HttpTestingController
}
from '@angular/common/http/testing';
describe('TodoService', () => {
let service: TodoService;
let httpTestingController: HttpTestingController;
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [TodoService],
});
service = bed.get(TodoService);
httpTestingController = TestBed.get(HttpTestingController);
});
});
HttpClientTestingModule and HttpTestingController
import {
HttpClientTestingModule,
HttpTestingController
}
from '@angular/common/http/testing';
describe('TodoService', () => {
let service: TodoService;
let httpTestingController: HttpTestingController;
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [TodoService],
});
service = bed.get(TodoService);
httpTestingController = TestBed.get(HttpTestingController);
});
});
HttpClientTestingModule and HttpTestingController
import {
HttpClientTestingModule,
HttpTestingController
}
from '@angular/common/http/testing';
describe('TodoService', () => {
let service: TodoService;
let httpTestingController: HttpTestingController;
beforeEach(() => {
const bed = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [TodoService],
});
service = bed.get(TodoService);
httpTestingController = TestBed.get(HttpTestingController);
});
});
HttpClientTestingModule and HttpTestingController
it('should get todo items', async(() => {
service.getAll().subscribe(result => {
expect(result).toEqual(mockTodoData.todoItems);
expect(result.length).toBe(mockTodoData.todoItems.length);
});
req.flush(mockTodoData.todoItems);
httpTestingController.verify();
}));
Using MockBackend in Test
it('should get todo items', async(() => {
service.getAll().subscribe(result => {
expect(result).toEqual(mockTodoData.todoItems);
expect(result.length).toBe(mockTodoData.todoItems.length);
});
req.flush(mockTodoData.todoItems);
httpTestingController.verify();
}));
Using MockBackend in Test
it('should get todo items', async(() => {
service.getAll().subscribe(result => {
expect(result).toEqual(mockTodoData.todoItems);
expect(result.length).toBe(mockTodoData.todoItems.length);
});
req.flush(mockTodoData.todoItems);
httpTestingController.verify();
}));
Using MockBackend in Test
it('should get todo items', async(() => {
service.getAll().subscribe(result => {
expect(result).toEqual(mockTodoData.todoItems);
expect(result.length).toBe(mockTodoData.todoItems.length);
});
req.flush(mockTodoData.todoItems);
httpTestingController.verify();
}));
Using MockBackend in Test
it('should get todo items', async(() => {
service.getAll().subscribe(result => {
expect(result).toEqual(mockTodoData.todoItems);
expect(result.length).toBe(mockTodoData.todoItems.length);
});
req.flush(mockTodoData.todoItems);
httpTestingController.verify();
}));
Using MockBackend in Test
it('should get todo items', async(() => {
service.getAll().subscribe(result => {
expect(result).toEqual(mockTodoData.todoItems);
expect(result.length).toBe(mockTodoData.todoItems.length);
});
req.flush( mockTodoData.todoItems);
httpTestingController.verify();
}));
Using MockBackend in Test
afterEach(() => {
httpTestingController.verify();
})
Verify All Http Calls Are Flushed
afterEach(() => {
httpTestingController.verify();
})
Verify All Http Calls Are Flushed
digitaldrummerjhttps://digitaldrummerj.me
HTTP Service Testing Recap
• Don’t call backend
• Use HttpClientTestingModule and HttpTestingController
• Don't forget to call verify to ensure no pending calls
• Not worth testing if no logic other than http
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerjhttps://digitaldrummerj.me
Goal Is To Test Navigation
Not Angular Router
16
digitaldrummerjhttps://digitaldrummerj.me
Abstract Router Complexity
17
import { RouterTestingModule } from '@angular/router/testing';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{ path: '', component: HeaderComponent },
{ path: 'login', children: [], component: HeaderComponent },
{ path: 'signup', component: HeaderComponent },
{ path: '**', component: HeaderComponent }
]),
],
declarations: [
HeaderComponent,
]
})
.compileComponents();
}));
RouterTestingModule Setup Part 1 of 2
import { RouterTestingModule } from '@angular/router/testing';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{ path: '', component: HeaderComponent },
{ path: 'login', children: [], component: HeaderComponent },
{ path: 'signup', component: HeaderComponent },
{ path: '**', component: HeaderComponent }
]),
],
declarations: [
HeaderComponent,
]
})
.compileComponents();
}));
RouterTestingModule Setup Part 1 of 2
import { RouterTestingModule } from '@angular/router/testing';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{ path: '', component: HeaderComponent },
{ path: 'login', children: [], component: HeaderComponent },
{ path: 'signup', component: HeaderComponent },
{ path: '**', component: HeaderComponent }
]),
],
declarations: [
HeaderComponent,
]
})
.compileComponents();
}));
RouterTestingModule Setup Part 1 of 2
import { RouterTestingModule } from '@angular/router/testing';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{ path: '', component: HeaderComponent },
{ path: 'login', children: [], component: HeaderComponent },
{ path: 'signup', component: HeaderComponent },
{ path: '**', component: HeaderComponent }
]),
],
declarations: [
HeaderComponent,
]
})
.compileComponents();
}));
RouterTestingModule Setup Part 1 of 2
import { RouterTestingModule } from '@angular/router/testing';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{ path: '', component: HeaderComponent },
{ path: 'login', children: [], component: HeaderComponent },
{ path: 'signup', component: HeaderComponent },
{ path: '**', component: HeaderComponent }
]),
],
declarations: [
HeaderComponent,
]
})
.compileComponents();
}));
RouterTestingModule Setup Part 1 of 2
import { SpyLocation } from '@angular/common/testing';
import { Location } from '@angular/common';
import { RouterLinkWithHref } from '@angular/router';
let location: SpyLocation;
let allLinkDes: DebugElement[];
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
element = fixture.debugElement;
component = element.componentInstance;
const injector = fixture.debugElement.injector;
location = injector.get(Location) as SpyLocation;
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
fixture.detectChanges();
});
RouterTestingModule Setup Part 2 of 2
import { SpyLocation } from '@angular/common/testing';
import { Location } from '@angular/common';
import { RouterLinkWithHref } from '@angular/router';
let location: SpyLocation;
let allLinkDes: DebugElement[];
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
element = fixture.debugElement;
component = element.componentInstance;
const injector = fixture.debugElement.injector;
location = injector.get(Location) as SpyLocation;
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
fixture.detectChanges();
});
RouterTestingModule Setup Part 2 of 2
import { SpyLocation } from '@angular/common/testing';
import { Location } from '@angular/common';
import { RouterLinkWithHref } from '@angular/router';
let location: SpyLocation;
let allLinkDes: DebugElement[];
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
element = fixture.debugElement;
component = element.componentInstance;
const injector = fixture.debugElement.injector;
location = injector.get(Location) as SpyLocation;
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
fixture.detectChanges();
});
RouterTestingModule Setup Part 2 of 2
import { SpyLocation } from '@angular/common/testing';
import { Location } from '@angular/common';
import { RouterLinkWithHref } from '@angular/router';
let location: SpyLocation;
let allLinkDes: DebugElement[];
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
element = fixture.debugElement;
component = element.componentInstance;
const injector = fixture.debugElement.injector;
location = injector.get(Location) as SpyLocation;
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
fixture.detectChanges();
});
RouterTestingModule Setup Part 2 of 2
import { SpyLocation } from '@angular/common/testing';
import { Location } from '@angular/common';
import { RouterLinkWithHref } from '@angular/router';
let location: SpyLocation;
let allLinkDes: DebugElement[];
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
element = fixture.debugElement;
component = element.componentInstance;
const injector = fixture.debugElement.injector;
location = injector.get(Location) as SpyLocation;
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
fixture.detectChanges();
});
RouterTestingModule Setup Part 2 of 2
import { SpyLocation } from '@angular/common/testing';
import { Location } from '@angular/common';
import { RouterLinkWithHref } from '@angular/router';
let location: SpyLocation;
let allLinkDes: DebugElement[];
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
element = fixture.debugElement;
component = element.componentInstance;
const injector = fixture.debugElement.injector;
location = injector.get(Location) as SpyLocation;
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
fixture.detectChanges();
});
RouterTestingModule Setup Part 2 of 2
import {advance, expectPathToBe, click } from '../../../testing';
it('all items takes me home', fakeAsync(() => {
const linkDes = allLinkDes[4];
expectPathToBe(location, '', 'link should not have navigated yet');
click(linkDes);
advance(fixture);
expectPathToBe(location, '/signup');
}));
Test Route Worked
import {advance, expectPathToBe, click } from '../../../testing';
it('all items takes me home', fakeAsync(() => {
const linkDes = allLinkDes[4];
expectPathToBe(location, '', 'link should not have navigated yet');
click(linkDes);
advance(fixture);
expectPathToBe(location, '/signup');
}));
Test Route Worked
import {advance, expectPathToBe, click } from '../../../testing';
it('all items takes me home', fakeAsync(() => {
const linkDes = allLinkDes[4];
expectPathToBe(location, '', 'link should not have navigated yet');
click(linkDes);
advance(fixture);
expectPathToBe(location, '/signup');
}));
Test Route Worked
import {advance, expectPathToBe, click } from '../../../testing';
it('all items takes me home', fakeAsync(() => {
const linkDes = allLinkDes[4];
expectPathToBe(location, '', 'link should not have navigated yet');
click(linkDes);
advance(fixture);
expectPathToBe(location, '/signup');
}));
Test Route Worked
import {advance, expectPathToBe, click } from '../../../testing';
it('all items takes me home', fakeAsync(() => {
const linkDes = allLinkDes[4];
expectPathToBe(location, '', 'link should not have navigated yet');
click(linkDes);
advance(fixture);
expectPathToBe(location, '/signup');
}));
Test Route Worked
import {advance, expectPathToBe, click } from '../../../testing';
it('all items takes me home', fakeAsync(() => {
const linkDes = allLinkDes[4];
expectPathToBe(location, '', 'link should not have navigated yet');
click(linkDes);
advance(fixture);
expectPathToBe(location, '/signup');
}));
Test Route Worked
import {advance, expectPathToBe, click } from '../../../testing';
it('all items takes me home', fakeAsync(() => {
const linkDes = allLinkDes[4];
expectPathToBe(location, '', 'link should not have navigated yet');
click(linkDes);
advance(fixture);
expectPathToBe(location, '/signup');
}));
Test Route Worked
export const ButtonClickEvents = {
left: { button: 0 },
right: { button: 2 }
};
export function click(
el: DebugElement | HTMLElement,
eventObj: any = ButtonClickEvents.left): void
{
if (el instanceof HTMLElement) {
el.click();
} else {
el.triggerEventHandler('click', eventObj);
}
}
Helper: Click Function
export const ButtonClickEvents = {
left: { button: 0 },
right: { button: 2 }
};
export function click(
el: DebugElement | HTMLElement,
eventObj: any = ButtonClickEvents.left): void
{
if (el instanceof HTMLElement) {
el.click();
} else {
el.triggerEventHandler('click', eventObj);
}
}
Helper: Click Function
export const ButtonClickEvents = {
left: { button: 0 },
right: { button: 2 }
};
export function click(
el: DebugElement | HTMLElement,
eventObj: any = ButtonClickEvents.left): void
{
if (el instanceof HTMLElement) {
el.click();
} else {
el.triggerEventHandler('click', eventObj);
}
}
Helper: Click Function
export const ButtonClickEvents = {
left: { button: 0 },
right: { button: 2 }
};
export function click(
el: DebugElement | HTMLElement,
eventObj: any = ButtonClickEvents.left): void
{
if (el instanceof HTMLElement) {
el.click();
} else {
el.triggerEventHandler('click', eventObj);
}
}
Helper: Click Function
export function advance(f: ComponentFixture<any>, time?: number): void {
if (time) {
tick(time);
} else {
tick();
}
f.detectChanges();
}
Helper: Advance Function
export function advance(f: ComponentFixture<any>, time?: number): void {
if (time) {
tick(time);
} else {
tick();
}
f.detectChanges();
}
Helper: Advance Function
export function advance(f: ComponentFixture<any>, time?: number): void {
if (time) {
tick(time);
} else {
tick();
}
f.detectChanges();
}
Helper: Advance Function
export function advance(f: ComponentFixture<any>, time?: number): void {
if (time) {
tick(time);
} else {
tick();
}
f.detectChanges();
}
Helper: Advance Function
import { Location } from '@angular/common';
export function expectPathToBe(
l: Location,
path: string,
expectationFailOutput?: any)
{
expect(l.path()).toEqual(path, expectationFailOutput || l.path());
}
Helper: expectPathToBe function
import { Location } from '@angular/common';
export function expectPathToBe(
l: Location,
path: string,
expectationFailOutput?: any)
{
expect(l.path()).toEqual(path, expectationFailOutput || l.path());
}
Helper: expectPathToBe function
import { Location } from '@angular/common';
export function expectPathToBe(
l: Location,
path: string,
expectationFailOutput?: any)
{
expect(l.path()).toEqual(path, expectationFailOutput || l.path());
}
Helper: expectPathToBe function
import { Location } from '@angular/common';
export function expectPathToBe(
l: Location,
path: string,
expectationFailOutput?: any)
{
expect(l.path()).toEqual(path, expectationFailOutput || l.path());
}
Helper: expectPathToBe function
digitaldrummerjhttps://digitaldrummerj.me
Routing Recap
• Don’t test Router. Test navigation
• Use RouterTestingModule
• Set routes to same component for testing setup ease
digitaldrummerjhttps://digitaldrummerj.me
fdescribe('MyTest Suite', () => {
fit('My Test', () => {
});
});
Run Only This
fdescribe('MyTest Suite', () => {
fit('My Test', () => {
});
});
Run Only This
xdescribe('MyTest Suite', () => {
xit('My Test', () => {
});
});
Don't Run This
xdescribe('MyTest Suite', () => {
xit('My Test', () => {
});
});
Exclude Test
describe('HeaderComponent', () => {
setup();
describe('Navigation Test', navigationTests);
describe('Create Test', createTest);
describe('Toggle Menu Test', toggleMenuTest);
});
function setup() {
beforeEach(async(() => {
}));
beforeEach(() => {
});
}
function navigationTests() {
beforeEach(() => {
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
});
}
Grouping Tests
describe('HeaderComponent', () => {
setup();
describe('Navigation Test', navigationTests);
describe('Create Test', createTest);
describe('Toggle Menu Test', toggleMenuTest);
});
function setup() {
beforeEach(async(() => {
}));
beforeEach(() => {
});
}
function navigationTests() {
beforeEach(() => {
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
});
}
Grouping Tests
describe('HeaderComponent', () => {
setup();
describe('Navigation Test', navigationTests);
describe('Create Test', createTest);
describe('Toggle Menu Test', toggleMenuTest);
});
function setup() {
beforeEach(async(() => {
}));
beforeEach(() => {
});
}
function navigationTests() {
beforeEach(() => {
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
});
}
Grouping Tests
describe('HeaderComponent', () => {
setup();
describe('Navigation Test', navigationTests);
describe('Create Test', createTest);
describe('Toggle Menu Test', toggleMenuTest);
});
function setup() {
beforeEach(async(() => {
}));
beforeEach(() => {
});
}
function navigationTests() {
beforeEach(() => {
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
});
}
Grouping Tests
describe('HeaderComponent', () => {
setup();
describe('Navigation Test', navigationTests);
describe('Create Test', createTest);
describe('Toggle Menu Test', toggleMenuTest);
});
function setup() {
beforeEach(async(() => {
}));
beforeEach(() => {
});
}
function navigationTests() {
beforeEach(() => {
allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
});
}
Grouping Tests
digitaldrummerjhttps://digitaldrummerj.me
toEqual
doesn't take additional message
expect(data).toEqual(expectedData, 'Some Message');
toEqual
digitaldrummerjhttps://digitaldrummerj.me
Jasmine Goodies Recap
• f = only run this
• x = don't run this
• Make sure to not check f or x test in
• Group like test or test that have expensive setup cost
• Additional message doesn’t work for toEqual
digitaldrummerjhttps://digitaldrummerj.me
Unit Tests Are Proof That My
Code Does What I Think It Does
21
digitaldrummerjhttps://digitaldrummerj.me
Release Faster
21
digitaldrummerjhttps://digitaldrummerj.me
Higher Confidence
21
digitaldrummerjhttps://digitaldrummerj.me
Refactor with Ease
21
digitaldrummerjhttps://digitaldrummerj.me
Confirm Functionality
21
digitaldrummerjhttps://digitaldrummerj.me
Check Regressions
22
digitaldrummerjhttps://digitaldrummerj.me
Pinpoint Bugs
22
digitaldrummerjhttps://digitaldrummerj.me
Document Functionality
22
digitaldrummerjhttps://digitaldrummerj.me
Keep Them Fast
digitaldrummerjhttps://digitaldrummerj.me
Keep Them Simple
digitaldrummerjhttps://digitaldrummerj.me
Keep Them Consistent
digitaldrummerjhttps://digitaldrummerj.me
"Don't let the fear that testing can't catch
all bugs stop you from writing the tests
that will catch most bugs"
Martin Fowler
Refactoring
22
digitaldrummerjhttps://digitaldrummerj.me
Resources
Unit Testing Guide: angular.io/guide/testing and angular.io/guide/http#testing-
http-requests
Jasmine Intro: jasmine.github.io/2.0/introduction.html
Slides: slideshare.net/digitaldrummerj/ndc-minnesota-2018-ng-unit-testing
Code: github.com/digitaldrummerj/angular-tutorial-code/tree/chapter-unit-test
22
https://digitaldrummerj.me
/digitaldrummerj
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerj.me
digitaldrummerj
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerj.me
digitaldrummerj
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerj.me
digitaldrummerj
digitaldrummerjhttps://digitaldrummerj.me
digitaldrummerjhttps://digitaldrummerj.me
Component with Inputs and Outputs
The goal is to ensure that binding works as expected
Test input: simply update value and ensure it renders
Test output: trigger triggerEventHandler and subscribe to
the EventEmitter
23
@Component({
selector: 'app-input-output',
template: `
<h1>Hello {{subject}}!</h1>
<button (click)="depart()">We Out</button>
`
})
export class InputOutputComponent {
@Input('subject') subject: string;
@Output('leave') leave: EventEmitter<string> = new EventEmitter();
depart() {
this.leave.emit(`Later ${this.subject}!`);
}
}
Component
@Component({
selector: 'app-input-output',
template: `
<h1>Hello {{subject}}!</h1>
<button (click)="depart()">We Out</button>
`
})
export class InputOutputComponent {
@Input('subject') subject: string;
@Output('leave') leave: EventEmitter<string> = new EventEmitter();
depart() {
this.leave.emit(`Later ${this.subject}!`);
}
}
Component
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [ InputOutputComponent ]
})
.createComponent(InputOutputComponent);
component = fixture.componentInstance;
de = fixture.debugElement;
button = de.query(By.css('button'));
component.subject = 'galaxy';
fixture.detectChanges();
});
Setup
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [ InputOutputComponent ]
})
.createComponent(InputOutputComponent);
component = fixture.componentInstance;
de = fixture.debugElement;
button = de.query(By.css('button'));
component.subject = 'galaxy';
fixture.detectChanges();
});
Setup
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [ InputOutputComponent ]
})
.createComponent(InputOutputComponent);
component = fixture.componentInstance;
de = fixture.debugElement;
button = de.query(By.css('button'));
component.subject = 'galaxy';
fixture.detectChanges();
});
Setup
it('has `subject` as an @Input', () => {
expect(component.subject).toBe('galaxy');
});
Input
it('says goodbye to the `subject`', () => {
let farewell;
component.leave.subscribe(event => farewell = event);
button.triggerEventHandler('click', { button: 0 });
expect(farewell).toBe('Later galaxy!');
});
Output
it('says goodbye to the `subject`', () => {
let farewell;
component.leave.subscribe(event => farewell = event);
button.triggerEventHandler('click', { button: 0 });
expect(farewell).toBe('Later galaxy!');
});
Output
it('says goodbye to the `subject`', () => {
let farewell;
component.leave.subscribe(event => farewell = event);
button.triggerEventHandler('click', { button: 0 });
expect(farewell).toBe('Later galaxy!');
});
Output
formErrors = {
'item': ''
};
validationMessages = {
'item': {
'required': 'Item is required.',
'minlength': 'Item must be at least 3 characters'
}
};
Error Messages
formErrors = {
'item': ''
};
validationMessages = {
'item': {
'required': 'Item is required.',
'minlength': 'Item must be at least 3 characters'
}
};
Error Messages
onValueChanged(data?: any) {
const form = this.addForm;
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' ';
}
}
}
}
Check Validation on Field Change
onValueChanged(data?: any) {
const form = this.addForm;
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' ';
}
}
}
}
Check Validation on Field Change
onValueChanged(data?: any) {
const form = this.addForm;
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' ';
}
}
}
}
Check Validation on Field Change
onValueChanged(data?: any) {
const form = this.addForm;
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' ';
}
}
}
}
Check Validation on Field Change
onValueChanged(data?: any) {
const form = this.addForm;
for (const field in this.formErrors) {
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' ';
}
}
}
}
Check Validation on Field Change
digitaldrummerjhttps://digitaldrummerj.me
Override Provider With
useValue or useClass
25
digitaldrummerjhttps://digitaldrummerj.me
Create Fixture
Create component instance with TestBed.createComponent
Returns test fixture
Closes TestBed to further configuration
25
digitaldrummerjhttps://digitaldrummerj.me
Configure Test Module
TestBed creates Angular testing module
TestBed.configureTestingModule configures module
Use BeforeEach to reset module before each test
25
digitaldrummerjhttps://digitaldrummerj.me
Component Instance
fixture.componentInstance to access to component
25
digitaldrummerjhttps://digitaldrummerj.me
Change Detection
Fixture.detectChanges triggers change detection
Note: TestBed.createComponent does not trigger
25
digitaldrummerjhttps://digitaldrummerj.me
Component DOM Access
DebugElement to access the component's DOM
DebugElement.query to query the DOM
By.css to query DOM using CSS selectors
25
digitaldrummerjhttps://digitaldrummerj.me
Fast
Failures Point
to Problems
Easy to Read
and Write
ConsistentIsolatedAutomated
Tenants
25

Angular Unit Testing NDC Minn 2018

  • 1.
  • 2.
    digitaldrummerjhttps://digitaldrummerj.me Unit Tests AreProof That My Code Does What I Think It Does 2
  • 3.
  • 4.
  • 5.
  • 6.
    digitaldrummerjhttps://digitaldrummerj.me We only getadvantages when we are comfortable writing good tests Disclaimer
  • 7.
  • 8.
  • 9.
  • 10.
    digitaldrummerjhttps://digitaldrummerj.me Well NamedEasy ToWrite Easy To ReadNot Brittle Maintainable Qualities 10
  • 11.
    digitaldrummerjhttps://digitaldrummerj.me Tests Right ThingsContinuedRelevance IsolatedConsistent Results Dependable Qualities 11
  • 12.
  • 13.
    digitaldrummerjhttps://digitaldrummerj.me End to EndTests (~10%) Unit Tests (~70%) Integration Tests (~20%) Testing Pyramid
  • 14.
  • 15.
  • 16.
  • 17.
    digitaldrummerjhttps://digitaldrummerj.me http://nodejs.org Node JS (8+) npminstall -g @angular/cli NPM Global Packages ng new AppName --style scss --routing Create New Project npm test Unit Test Angular CLI Setup 17
  • 18.
    digitaldrummerjhttps://digitaldrummerj.me Test Runner TestFramework Testing Utilities Karma Tools Jasmine Angular 18
  • 19.
  • 20.
  • 21.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 22.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 23.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 24.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 25.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 26.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 27.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 28.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 29.
    describe('First spec', ()=> { it('should pass', () => { expect(true).toBeTruthy(); }); }); Simple Passing Test
  • 30.
  • 31.
  • 32.
  • 33.
    digitaldrummerjhttps://digitaldrummerj.me TestBed Configure Test Module GetReferences Trigger Change Detection Access to DOM Elements
  • 34.
    import { TestBed} from '@angular/core/testing'; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [], declarations: [], providers: [], schemas: [NO_ERRORS_SCHEMA], }); }); TestBed Config Layout
  • 35.
    import { TestBed} from '@angular/core/testing'; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [], declarations: [], providers: [], schemas: [NO_ERRORS_SCHEMA], }); }); TestBed Config Layout
  • 36.
  • 37.
    import { Component,OnInit } from '@angular/core'; @Component({ selector: 'app-simple', templateUrl: './simple.component.html', styleUrls: ['./simple.component.scss'] }) export class SimpleComponent implements OnInit { subject = 'World'; constructor() { } ngOnInit() { } } Component
  • 38.
    import { Component,OnInit } from '@angular/core'; @Component({ selector: 'app-simple', templateUrl: './simple.component.html', styleUrls: ['./simple.component.scss'] }) export class SimpleComponent implements OnInit { subject = 'World'; constructor() { } ngOnInit() { } } Component
  • 39.
    import { Component,OnInit } from '@angular/core'; @Component({ selector: 'app-simple', templateUrl: './simple.component.html', styleUrls: ['./simple.component.scss'] }) export class SimpleComponent implements OnInit { subject = 'World'; constructor() { } ngOnInit() { } } Component
  • 40.
  • 41.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); }); TestBed
  • 42.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); }); TestBed
  • 43.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); }); TestBed
  • 44.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); }); TestBed
  • 45.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); }); TestBed
  • 46.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); }); TestBed
  • 47.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); }); TestBed
  • 48.
  • 49.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { let fixture: ComponentFixture<SimpleComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); }); }); Create Component
  • 50.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { let fixture: ComponentFixture<SimpleComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); }); }); Create Component
  • 51.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { let fixture: ComponentFixture<SimpleComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); }); }); Create Component
  • 52.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { let fixture: ComponentFixture<SimpleComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); }); }); Create Component
  • 53.
  • 54.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { let fixture: ComponentFixture<SimpleComponent>; let component: SimpleComponent; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); component = fixture.componentInstance; }); }); Create Component
  • 55.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { let fixture: ComponentFixture<SimpleComponent>; let component: SimpleComponent; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); component = fixture.componentInstance; }); }); Create Component
  • 56.
    it('should be created',() => { expect(component).toBeTruthy(); }); Component Created Test
  • 57.
    it('should be created',() => { expect(component).toBeTruthy(); }); Component Created Test
  • 58.
    digitaldrummerjhttps://digitaldrummerj.me Configure Test Module CreateFixture Get Component Instance Trigger Change Detection 58
  • 59.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { let component: SimpleComponent; let fixture: ComponentFixture<SimpleComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); component = fixture.componentInstance; fixture.detectChanges(); }); }); Create Component
  • 60.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; describe('SimpleComponent', () => { let component: SimpleComponent; let fixture: ComponentFixture<SimpleComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); component = fixture.componentInstance; fixture.detectChanges(); }); }); Create Component
  • 61.
    digitaldrummerjhttps://digitaldrummerj.me Configure Test Module CreateFixture Get Component Instance Trigger Change Detection Access Component's DOM 61
  • 62.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; describe('SimpleComponent', () => { let component: SimpleComponent; let fixture: ComponentFixture<SimpleComponent>; let element: DebugElement; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); component = fixture.componentInstance; element = fixture.debugElement; fixture.detectChanges(); }); }); Element Debug
  • 63.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; describe('SimpleComponent', () => { let component: SimpleComponent; let fixture: ComponentFixture<SimpleComponent>; let element: DebugElement; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); component = fixture.componentInstance; element = fixture.debugElement; fixture.detectChanges(); }); }); Element Debug
  • 64.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; describe('SimpleComponent', () => { let component: SimpleComponent; let fixture: ComponentFixture<SimpleComponent>; let element: DebugElement; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); component = fixture.componentInstance; element = fixture.debugElement; fixture.detectChanges(); }); }); Element Debug
  • 65.
    import { async,ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleComponent } from './simple.component'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; describe('SimpleComponent', () => { let component: SimpleComponent; let fixture: ComponentFixture<SimpleComponent>; let element: DebugElement; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SimpleComponent ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponent); component = fixture.componentInstance; element = fixture.debugElement; fixture.detectChanges(); }); }); Element Debug
  • 66.
    <h1>Hello {{ subject}}!</h1> Component Html
  • 67.
    it('greets the subject',() => { const h1 = element.query(By.css('h1')); expect(h1.nativeElement.innerText).toBe('Hello world!'); }); Test Element Value
  • 68.
    it('greets the subject',() => { const h1 = element.query(By.css('h1')); expect(h1.nativeElement.innerText).toBe('Hello world!'); }); Test Element Value
  • 69.
    it('greets the subject',() => { const h1 = element.query(By.css('h1')); expect(h1.nativeElement.innerText).toBe('Hello world!'); }); Test Element Value
  • 70.
    digitaldrummerjhttps://digitaldrummerj.me TestBed Recap • ConfiguresModule • Create Component • Accesses DOM • Get injector references using TestBed.get • Change detection not triggered by createdComponent
  • 71.
  • 72.
    import { Injectable} from '@angular/core'; @Injectable() export class GreetingService { subject = 'world'; constructor() { } } Simple Service
  • 73.
    import { Injectable} from '@angular/core'; @Injectable() export class GreetingService { subject = 'world'; constructor() { } } Simple Service
  • 74.
    import { Injectable} from '@angular/core'; @Injectable() export class GreetingService { subject = 'world'; constructor() { } } Simple Service
  • 75.
    import { Component,OnInit } from '@angular/core'; import { GreetingService } from '../shared/services/greeting.service'; @Component({ selector: 'app-simple', templateUrl: './simple.component.html', styleUrls: ['./simple.component.scss'] }) export class SimpleComponent implements OnInit { subject = this.service.subject; constructor(private service: GreetingService) { } ngOnInit() { } } Component with Service
  • 76.
    import { Component,OnInit } from '@angular/core'; import { GreetingService } from '../shared/services/greeting.service'; @Component({ selector: 'app-simple', templateUrl: './simple.component.html', styleUrls: ['./simple.component.scss'] }) export class SimpleComponent implements OnInit { subject = this.service.subject; constructor(private service: GreetingService) { } ngOnInit() { } } Component with Service
  • 77.
    import { Component,OnInit } from '@angular/core'; import { GreetingService } from '../shared/services/greeting.service'; @Component({ selector: 'app-simple', templateUrl: './simple.component.html', styleUrls: ['./simple.component.scss'] }) export class SimpleComponent implements OnInit { subject = this.service.subject; constructor(private service: GreetingService) { } ngOnInit() { } } Component with Service
  • 78.
    import { Component,OnInit } from '@angular/core'; import { GreetingService } from '../shared/services/greeting.service'; @Component({ selector: 'app-simple', templateUrl: './simple.component.html', styleUrls: ['./simple.component.scss'] }) export class SimpleComponent implements OnInit { subject = this.service.subject; constructor(private service: GreetingService) { } ngOnInit() { } } Component with Service
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
    export class GreetingServiceStub{ public subject = 'Test World'; } Greeting Service Stub 8
  • 84.
    describe('SimpleComponentWithServiceComponent', () =>{ let component: SimpleComponentWithServiceComponent; let fixture: ComponentFixture<SimpleComponentWithServiceComponent>; let element: DebugElement; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SimpleComponentWithServiceComponent], providers: [{ provide: GreetingService, useClass: GreetingServiceStub }] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponentWithServiceComponent); component = fixture.componentInstance; element = fixture.debugElement; fixture.detectChanges(); }); }); Component With Service Setup
  • 85.
    describe('SimpleComponentWithServiceComponent', () =>{ let component: SimpleComponentWithServiceComponent; let fixture: ComponentFixture<SimpleComponentWithServiceComponent>; let element: DebugElement; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SimpleComponentWithServiceComponent], providers: [{ provide: GreetingService, useClass: GreetingServiceStub }] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponentWithServiceComponent); component = fixture.componentInstance; element = fixture.debugElement; fixture.detectChanges(); }); }); Component With Service Setup
  • 86.
    it('greets the subject',() => { const h1 = element.query(By.css('h1')); expect(h1.nativeElement.innerText).toBe('Hello Test World!'); }); Same Greet Test but With Service
  • 87.
    it('greets the subject',() => { const h1 = element.query(By.css('h1')); expect(h1.nativeElement.innerText).toBe('Hello Test World!'); }); Same Greet Test but With Service
  • 88.
  • 89.
    import { Injectable} from '@angular/core'; import { Observable } from 'rxjs/Rx'; @Injectable() export class GreetingService { constructor() { } getGreeting(): Observable<string> { return Observable.of('Hello'); } getSubject(): Observable<string> { return Observable.of('World'); } getPunctuation(): Observable<string> { return Observable.of('!'); } } Service
  • 90.
    import { Injectable} from '@angular/core'; import { Observable } from 'rxjs/Rx'; @Injectable() export class GreetingService { constructor() { } getGreeting(): Observable<string> { return Observable.of('Hello'); } getSubject(): Observable<string> { return Observable.of('World'); } getPunctuation(): Observable<string> { return Observable.of('!'); } } Service
  • 91.
    export class SimpleComponentWithServiceComponentimplements OnInit { subject: string; greeting: string; punctuation: string; constructor(private greetingService: GreetingService) { } ngOnInit() { this.greetingService.getSubject().subscribe((result) => { this.subject = result; }); this.greetingService.getGreeting().subscribe((result) => { this.greeting = result; }); this.greetingService.getPunctuation().subscribe((result) => { this.punctuation = result; }); } } Component
  • 92.
    export class SimpleComponentWithServiceComponentimplements OnInit { subject: string; greeting: string; punctuation: string; constructor(private greetingService: GreetingService) { } ngOnInit() { this.greetingService.getSubject().subscribe((result) => { this.subject = result; }); this.greetingService.getGreeting().subscribe((result) => { this.greeting = result; }); this.greetingService.getPunctuation().subscribe((result) => { this.punctuation = result; }); } } Component
  • 93.
    export class SimpleComponentWithServiceComponentimplements OnInit { subject: string; greeting: string; punctuation: string; constructor(private greetingService: GreetingService) { } ngOnInit() { this.greetingService.getSubject().subscribe((result) => { this.subject = result; }); this.greetingService.getGreeting().subscribe((result) => { this.greeting = result; }); this.greetingService.getPunctuation().subscribe((result) => { this.punctuation = result; }); } } Component
  • 94.
    let component: SimpleComponentWithServiceComponent; letfixture: ComponentFixture<SimpleComponentWithServiceComponent>; let element: DebugElement; let greetingService: GreetingService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SimpleComponentWithServiceComponent], providers: [GreetingService] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponentWithServiceComponent); comonent = fixture.componentInstance; element = fixture.debugElement; greetingService = element.injector.get(GreetingService); }); Setup
  • 95.
    let component: SimpleComponentWithServiceComponent; letfixture: ComponentFixture<SimpleComponentWithServiceComponent>; let element: DebugElement; let greetingService: GreetingService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SimpleComponentWithServiceComponent], providers: [GreetingService] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponentWithServiceComponent); comonent = fixture.componentInstance; element = fixture.debugElement; greetingService = element.injector.get(GreetingService); }); Setup
  • 96.
    let component: SimpleComponentWithServiceComponent; letfixture: ComponentFixture<SimpleComponentWithServiceComponent>; let element: DebugElement; let greetingService: GreetingService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SimpleComponentWithServiceComponent], providers: [GreetingService] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponentWithServiceComponent); comonent = fixture.componentInstance; element = fixture.debugElement; greetingService = element.injector.get(GreetingService); }); Setup
  • 97.
    let component: SimpleComponentWithServiceComponent; letfixture: ComponentFixture<SimpleComponentWithServiceComponent>; let element: DebugElement; let greetingService: GreetingService; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SimpleComponentWithServiceComponent], providers: [GreetingService] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleComponentWithServiceComponent); comonent = fixture.componentInstance; element = fixture.debugElement; greetingService = element.injector.get(GreetingService); }); Setup
  • 98.
    it('spying on greets',async(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(component.greeting).toBe('hello'); }); })); Component Test with SpyOn
  • 99.
    it('spying on greets',async(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(component.greeting).toBe('hello'); }); })); Component Test with SpyOn
  • 100.
    it('spying on greets',async(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(component.greeting).toBe('hello'); }); })); Component Test with SpyOn
  • 101.
    it('spying on greets',async(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(component.greeting).toBe('hello'); }); })); Component Test with SpyOn
  • 102.
    it('spying on greets',async(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(component.greeting).toBe('hello'); }); })); Component Test with SpyOn
  • 103.
    it('spying on greets',async(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(component.greeting).toBe('hello'); }); })); Component Test with SpyOn
  • 104.
    it('spying on greets',async(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); expect(component.greeting).toBe('hello'); }); })); Component Test with SpyOn
  • 105.
  • 106.
    import { tick,fakeAsync } from '@angular/core/testing'; it('fakeasync test', fakeAsync(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); tick(); fixture.detectChanges(); expect(component.greeting).toBe('hello'); })); Test Using FakeAsync
  • 107.
    import { tick,fakeAsync } from '@angular/core/testing'; it('fakeasync test', fakeAsync(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); tick(); fixture.detectChanges(); expect(component.greeting).toBe('hello'); })); Test Using FakeAsync
  • 108.
    import { tick,fakeAsync } from '@angular/core/testing'; it('fakeasync test', fakeAsync(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); tick(); fixture.detectChanges(); expect(component.greeting).toBe('hello'); })); Test Using FakeAsync
  • 109.
    import { tick,fakeAsync } from '@angular/core/testing'; it('fakeasync test', fakeAsync(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); tick(); fixture.detectChanges(); expect(component.greeting).toBe('hello'); })); Test Using FakeAsync
  • 110.
    import { tick,fakeAsync } from '@angular/core/testing'; it('fakeasync test', fakeAsync(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); tick(); fixture.detectChanges(); expect(component.greeting).toBe('hello'); })); Test Using FakeAsync
  • 111.
    import { tick,fakeAsync } from '@angular/core/testing'; it('fakeasync test', fakeAsync(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); tick(); fixture.detectChanges(); expect(component.gretting).toBe('hello'); })); Test Using FakeAsync
  • 112.
    import { tick,fakeAsync } from '@angular/core/testing'; it('fakeasync test', fakeAsync(() => { spyOn(greetingService, 'getGreeting').and.returnValue(Observable.of('hello')); fixture.detectChanges(); tick(); fixture.detectChanges(); expect(component.greeting).toBe('hello'); })); Test Using FakeAsync
  • 113.
    digitaldrummerjhttps://digitaldrummerj.me Component with ServiceRecap • Use Spy or Stubs • If spy, make sure to spy on everything in ngOnInit • Make async code look more sync like with fakeAsync
  • 114.
  • 115.
    digitaldrummerjhttps://digitaldrummerj.me Reactive Forms Tests ValidationErrors Field Validity Form Validity Error Messages Error Message Display 11
  • 116.
    import { FormGroup,FormBuilder, Validators } from '@angular/forms'; import {debounceTime} from 'rxjs/operators'; export class TodoComponent implements OnInit { addForm: FormGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit() { this.addForm = this.formBuilder.group({ 'item': ['', [Validators.required, Validators.minLength(3)]] }); this.addForm.valueChanges .pipe(debounceTime(1000)) .subscribe(data => this.onValueChanged(data) ); } } Reactive Form Setup
  • 117.
    import { FormGroup,FormBuilder, Validators } from '@angular/forms'; import {debounceTime} from 'rxjs/operators'; export class TodoComponent implements OnInit { addForm: FormGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit() { this.addForm = this.formBuilder.group({ 'item': ['', [Validators.required, Validators.minLength(3)]] }); this.addForm.valueChanges .pipe(debounceTime(1000)) .subscribe(data => this.onValueChanged(data) ); } } Reactive Form Setup
  • 118.
    import { FormGroup,FormBuilder, Validators } from '@angular/forms'; import {debounceTime} from 'rxjs/operators'; export class TodoComponent implements OnInit { addForm: FormGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit() { this.addForm = this.formBuilder.group({ 'item': ['', [Validators.required, Validators.minLength(3)]] }); this.addForm.valueChanges .pipe(debounceTime(1000)) .subscribe(data => this.onValueChanged(data) ); } } Reactive Form Setup
  • 119.
    import { FormGroup,FormBuilder, Validators } from '@angular/forms'; import {debounceTime} from 'rxjs/operators'; export class TodoComponent implements OnInit { addForm: FormGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit() { this.addForm = this.formBuilder.group({ 'item': ['', [Validators.required, Validators.minLength(3)]] }); this.addForm.valueChanges .pipe(debounceTime(1000)) .subscribe(data => this.onValueChanged(data) ); } } Reactive Form Setup
  • 120.
    import { FormGroup,FormBuilder, Validators } from '@angular/forms'; import {debounceTime} from 'rxjs/operators'; export class TodoComponent implements OnInit { addForm: FormGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit() { this.addForm = this.formBuilder.group({ 'item': ['', [Validators.required, Validators.minLength(3)]] }); this.addForm.valueChanges .pipe(debounceTime(1000)) .subscribe(data => this.onValueChanged(data) ); } } Reactive Form Setup
  • 121.
    import { ReactiveFormsModule} from '@angular/forms'; import { AbstractControl, Validators } from '@angular/forms'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule], declarations: [ TodoComponent ], providers: [ { provide: TodoService, useClass: MockTodoService } ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TodoComponent); element = fixture.debugElement; component = element.componentInstance; itemField = component.addForm.controls['item']; fixture.detectChanges(); }); TestBed Setup
  • 122.
    import { ReactiveFormsModule} from '@angular/forms'; import { AbstractControl, Validators } from '@angular/forms'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule], declarations: [ TodoComponent ], providers: [ { provide: TodoService, useClass: MockTodoService } ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TodoComponent); element = fixture.debugElement; component = element.componentInstance; itemField = component.addForm.controls['item']; fixture.detectChanges(); }); TestBed Setup
  • 123.
    import { ReactiveFormsModule} from '@angular/forms'; import { AbstractControl, Validators } from '@angular/forms'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule], declarations: [ TodoComponent ], providers: [ { provide: TodoService, useClass: MockTodoService } ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TodoComponent); element = fixture.debugElement; component = element.componentInstance; itemField = component.addForm.controls['item']; fixture.detectChanges(); }); TestBed Setup
  • 124.
    import { ReactiveFormsModule} from '@angular/forms'; import { AbstractControl, Validators } from '@angular/forms'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule], declarations: [ TodoComponent ], providers: [ { provide: TodoService, useClass: MockTodoService } ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TodoComponent); element = fixture.debugElement; component = element.componentInstance; itemField = component.addForm.controls['item']; fixture.detectChanges(); }); TestBed Setup
  • 125.
    it('item field requiredwith blank validity', () => { itemField.setValue(''); errors = itemField.errors || {}; expect(errors['required']).toBeDefined('required validator not triggered'); expect(errors['minlength']).toBeUndefined('min length validator was triggered'); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Test Field Validity
  • 126.
    it('item field requiredwith blank validity', () => { itemField.setValue(''); errors = itemField.errors || {}; expect(errors['required']).toBeDefined('required validator not triggered'); expect(errors['minlength']).toBeUndefined('min length validator was triggered'); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Test Field Validity
  • 127.
    it('item field requiredwith blank validity', () => { itemField.setValue(''); errors = itemField.errors || {}; expect(errors['required']).toBeDefined('required validator not triggered'); expect(errors['minlength']).toBeUndefined('min length validator was triggered'); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Test Field Validity
  • 128.
    it('item field requiredwith blank validity', () => { itemField.setValue(''); errors = itemField.errors || {}; expect(errors['required']).toBeDefined('required validator not triggered'); expect(errors['minlength']).toBeUndefined('min length validator was triggered'); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Test Field Validity
  • 129.
    it('item field requiredwith blank validity', () => { itemField.setValue(''); errors = itemField.errors || {}; expect(errors['required']).toBeDefined('required validator not triggered'); expect(errors['minlength']).toBeUndefined('min length validator was triggered'); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Test Field Validity
  • 130.
    it('item field minlength too short validity', () => { itemField.setValue('1'); errors = itemField.errors || {}; expect(errors['minlength']).toBeDefined(); expect(errors['required']).toBeUndefined(); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Min Length Validator
  • 131.
    it('item field minlength too short validity', () => { itemField.setValue('1'); errors = itemField.errors || {}; expect(errors['minlength']).toBeDefined(); expect(errors['required']).toBeUndefined(); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Min Length Validator
  • 132.
    it('item field minlength too short validity', () => { itemField.setValue('1'); errors = itemField.errors || {}; expect(errors['minlength']).toBeDefined(); expect(errors['required']).toBeUndefined(); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Min Length Validator
  • 133.
    it('item field minlength too short validity', () => { itemField.setValue('1'); errors = itemField.errors || {}; expect(errors['minlength']).toBeDefined(); expect(errors['required']).toBeUndefined(); expect(itemField.valid).toBeFalsy(); expect(component.addForm.valid).toBeFalsy(); }); Min Length Validator
  • 134.
  • 135.
  • 136.
    beforeEach(() => { itemField.markAsDirty(); expect(itemField.dirty).toBeTruthy('fieldshould be dirty'); fixture.detectChanges(); }); Mark Field as Dirty
  • 137.
    beforeEach(() => { itemField.markAsDirty(); expect(itemField.dirty).toBeTruthy('fieldshould be dirty'); fixture.detectChanges(); }); Mark Field as Dirty
  • 138.
    beforeEach(() => { itemField.markAsDirty(); expect(itemField.dirty).toBeTruthy('fieldshould be dirty'); fixture.detectChanges(); }); Mark Field as Dirty
  • 139.
    beforeEach(() => { itemField.markAsDirty(); expect(itemField.dirty).toBeTruthy('fieldshould be dirty'); fixture.detectChanges(); }); Mark Field as Dirty
  • 140.
  • 141.
    import { FormGroup,FormBuilder, Validators } from '@angular/forms'; import {debounceTime} from 'rxjs/operators'; export class TodoComponent implements OnInit { addForm: FormGroup; constructor(private formBuilder: FormBuilder) { } ngOnInit() { this.addForm = this.formBuilder.group({ 'item': ['', [Validators.required, Validators.minLength(3)]] }); this.addForm.valueChanges .pipe(debounceTime(1000)) .subscribe(data => this.onValueChanged(data) ); } } Reactive Form Setup
  • 142.
    it('item field minlength passes validity', fakeAsync(() => { itemField.setValue('123'); tick(1000); fixture.detectChanges(); expect(itemField.valid).toBeTruthy(); })); Proper Debounce Testing
  • 143.
    it('item field minlength passes validity', fakeAsync(() => { itemField.setValue('123'); tick(1000); fixture.detectChanges(); expect(itemField.valid).toBeTruthy(); })); Proper Debounce Testing
  • 144.
    it('item field minlength passes validity', fakeAsync(() => { itemField.setValue('123'); tick(1000); fixture.detectChanges(); expect(itemField.valid).toBeTruthy(); })); Proper Debounce Testing
  • 145.
    it('item field minlength passes validity', fakeAsync(() => { itemField.setValue('123'); tick(1000); fixture.detectChanges(); expect(itemField.valid).toBeTruthy(); })); Proper Debounce Testing
  • 146.
    it('item field minlength passes validity', fakeAsync(() => { itemField.setValue('123'); tick(1000); fixture.detectChanges(); expect(itemField.valid).toBeTruthy(); })); Proper Debounce Testing
  • 147.
    digitaldrummerjhttps://digitaldrummerj.me Reactive Form Recap •Test validation in component • Dirty flag not set when calling setValue • Make debounce work using tick(time)
  • 148.
  • 149.
  • 150.
  • 151.
    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; describe('TodoService',() => { let service: TodoService; let httpTestingController: HttpTestingController; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [TodoService], }); service = bed.get(TodoService); httpTestingController = TestBed.get(HttpTestingController); }); }); HttpClientTestingModule and HttpTestingController
  • 152.
    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; describe('TodoService',() => { let service: TodoService; let httpTestingController: HttpTestingController; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [TodoService], }); service = bed.get(TodoService); httpTestingController = TestBed.get(HttpTestingController); }); }); HttpClientTestingModule and HttpTestingController
  • 153.
    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; describe('TodoService',() => { let service: TodoService; let httpTestingController: HttpTestingController; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [TodoService], }); service = bed.get(TodoService); httpTestingController = TestBed.get(HttpTestingController); }); }); HttpClientTestingModule and HttpTestingController
  • 154.
    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; describe('TodoService',() => { let service: TodoService; let httpTestingController: HttpTestingController; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [TodoService], }); service = bed.get(TodoService); httpTestingController = TestBed.get(HttpTestingController); }); }); HttpClientTestingModule and HttpTestingController
  • 155.
    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; describe('TodoService',() => { let service: TodoService; let httpTestingController: HttpTestingController; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [TodoService], }); service = bed.get(TodoService); httpTestingController = TestBed.get(HttpTestingController); }); }); HttpClientTestingModule and HttpTestingController
  • 156.
    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; describe('TodoService',() => { let service: TodoService; let httpTestingController: HttpTestingController; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [TodoService], }); service = bed.get(TodoService); httpTestingController = TestBed.get(HttpTestingController); }); }); HttpClientTestingModule and HttpTestingController
  • 157.
    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; describe('TodoService',() => { let service: TodoService; let httpTestingController: HttpTestingController; beforeEach(() => { const bed = TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [TodoService], }); service = bed.get(TodoService); httpTestingController = TestBed.get(HttpTestingController); }); }); HttpClientTestingModule and HttpTestingController
  • 158.
    it('should get todoitems', async(() => { service.getAll().subscribe(result => { expect(result).toEqual(mockTodoData.todoItems); expect(result.length).toBe(mockTodoData.todoItems.length); }); req.flush(mockTodoData.todoItems); httpTestingController.verify(); })); Using MockBackend in Test
  • 159.
    it('should get todoitems', async(() => { service.getAll().subscribe(result => { expect(result).toEqual(mockTodoData.todoItems); expect(result.length).toBe(mockTodoData.todoItems.length); }); req.flush(mockTodoData.todoItems); httpTestingController.verify(); })); Using MockBackend in Test
  • 160.
    it('should get todoitems', async(() => { service.getAll().subscribe(result => { expect(result).toEqual(mockTodoData.todoItems); expect(result.length).toBe(mockTodoData.todoItems.length); }); req.flush(mockTodoData.todoItems); httpTestingController.verify(); })); Using MockBackend in Test
  • 161.
    it('should get todoitems', async(() => { service.getAll().subscribe(result => { expect(result).toEqual(mockTodoData.todoItems); expect(result.length).toBe(mockTodoData.todoItems.length); }); req.flush(mockTodoData.todoItems); httpTestingController.verify(); })); Using MockBackend in Test
  • 162.
    it('should get todoitems', async(() => { service.getAll().subscribe(result => { expect(result).toEqual(mockTodoData.todoItems); expect(result.length).toBe(mockTodoData.todoItems.length); }); req.flush(mockTodoData.todoItems); httpTestingController.verify(); })); Using MockBackend in Test
  • 163.
    it('should get todoitems', async(() => { service.getAll().subscribe(result => { expect(result).toEqual(mockTodoData.todoItems); expect(result.length).toBe(mockTodoData.todoItems.length); }); req.flush( mockTodoData.todoItems); httpTestingController.verify(); })); Using MockBackend in Test
  • 164.
  • 165.
  • 166.
    digitaldrummerjhttps://digitaldrummerj.me HTTP Service TestingRecap • Don’t call backend • Use HttpClientTestingModule and HttpTestingController • Don't forget to call verify to ensure no pending calls • Not worth testing if no logic other than http
  • 167.
  • 168.
    digitaldrummerjhttps://digitaldrummerj.me Goal Is ToTest Navigation Not Angular Router 16
  • 169.
  • 170.
    import { RouterTestingModule} from '@angular/router/testing'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([ { path: '', component: HeaderComponent }, { path: 'login', children: [], component: HeaderComponent }, { path: 'signup', component: HeaderComponent }, { path: '**', component: HeaderComponent } ]), ], declarations: [ HeaderComponent, ] }) .compileComponents(); })); RouterTestingModule Setup Part 1 of 2
  • 171.
    import { RouterTestingModule} from '@angular/router/testing'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([ { path: '', component: HeaderComponent }, { path: 'login', children: [], component: HeaderComponent }, { path: 'signup', component: HeaderComponent }, { path: '**', component: HeaderComponent } ]), ], declarations: [ HeaderComponent, ] }) .compileComponents(); })); RouterTestingModule Setup Part 1 of 2
  • 172.
    import { RouterTestingModule} from '@angular/router/testing'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([ { path: '', component: HeaderComponent }, { path: 'login', children: [], component: HeaderComponent }, { path: 'signup', component: HeaderComponent }, { path: '**', component: HeaderComponent } ]), ], declarations: [ HeaderComponent, ] }) .compileComponents(); })); RouterTestingModule Setup Part 1 of 2
  • 173.
    import { RouterTestingModule} from '@angular/router/testing'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([ { path: '', component: HeaderComponent }, { path: 'login', children: [], component: HeaderComponent }, { path: 'signup', component: HeaderComponent }, { path: '**', component: HeaderComponent } ]), ], declarations: [ HeaderComponent, ] }) .compileComponents(); })); RouterTestingModule Setup Part 1 of 2
  • 174.
    import { RouterTestingModule} from '@angular/router/testing'; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule.withRoutes([ { path: '', component: HeaderComponent }, { path: 'login', children: [], component: HeaderComponent }, { path: 'signup', component: HeaderComponent }, { path: '**', component: HeaderComponent } ]), ], declarations: [ HeaderComponent, ] }) .compileComponents(); })); RouterTestingModule Setup Part 1 of 2
  • 175.
    import { SpyLocation} from '@angular/common/testing'; import { Location } from '@angular/common'; import { RouterLinkWithHref } from '@angular/router'; let location: SpyLocation; let allLinkDes: DebugElement[]; beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); element = fixture.debugElement; component = element.componentInstance; const injector = fixture.debugElement.injector; location = injector.get(Location) as SpyLocation; allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); fixture.detectChanges(); }); RouterTestingModule Setup Part 2 of 2
  • 176.
    import { SpyLocation} from '@angular/common/testing'; import { Location } from '@angular/common'; import { RouterLinkWithHref } from '@angular/router'; let location: SpyLocation; let allLinkDes: DebugElement[]; beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); element = fixture.debugElement; component = element.componentInstance; const injector = fixture.debugElement.injector; location = injector.get(Location) as SpyLocation; allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); fixture.detectChanges(); }); RouterTestingModule Setup Part 2 of 2
  • 177.
    import { SpyLocation} from '@angular/common/testing'; import { Location } from '@angular/common'; import { RouterLinkWithHref } from '@angular/router'; let location: SpyLocation; let allLinkDes: DebugElement[]; beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); element = fixture.debugElement; component = element.componentInstance; const injector = fixture.debugElement.injector; location = injector.get(Location) as SpyLocation; allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); fixture.detectChanges(); }); RouterTestingModule Setup Part 2 of 2
  • 178.
    import { SpyLocation} from '@angular/common/testing'; import { Location } from '@angular/common'; import { RouterLinkWithHref } from '@angular/router'; let location: SpyLocation; let allLinkDes: DebugElement[]; beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); element = fixture.debugElement; component = element.componentInstance; const injector = fixture.debugElement.injector; location = injector.get(Location) as SpyLocation; allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); fixture.detectChanges(); }); RouterTestingModule Setup Part 2 of 2
  • 179.
    import { SpyLocation} from '@angular/common/testing'; import { Location } from '@angular/common'; import { RouterLinkWithHref } from '@angular/router'; let location: SpyLocation; let allLinkDes: DebugElement[]; beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); element = fixture.debugElement; component = element.componentInstance; const injector = fixture.debugElement.injector; location = injector.get(Location) as SpyLocation; allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); fixture.detectChanges(); }); RouterTestingModule Setup Part 2 of 2
  • 180.
    import { SpyLocation} from '@angular/common/testing'; import { Location } from '@angular/common'; import { RouterLinkWithHref } from '@angular/router'; let location: SpyLocation; let allLinkDes: DebugElement[]; beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); element = fixture.debugElement; component = element.componentInstance; const injector = fixture.debugElement.injector; location = injector.get(Location) as SpyLocation; allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); fixture.detectChanges(); }); RouterTestingModule Setup Part 2 of 2
  • 181.
    import {advance, expectPathToBe,click } from '../../../testing'; it('all items takes me home', fakeAsync(() => { const linkDes = allLinkDes[4]; expectPathToBe(location, '', 'link should not have navigated yet'); click(linkDes); advance(fixture); expectPathToBe(location, '/signup'); })); Test Route Worked
  • 182.
    import {advance, expectPathToBe,click } from '../../../testing'; it('all items takes me home', fakeAsync(() => { const linkDes = allLinkDes[4]; expectPathToBe(location, '', 'link should not have navigated yet'); click(linkDes); advance(fixture); expectPathToBe(location, '/signup'); })); Test Route Worked
  • 183.
    import {advance, expectPathToBe,click } from '../../../testing'; it('all items takes me home', fakeAsync(() => { const linkDes = allLinkDes[4]; expectPathToBe(location, '', 'link should not have navigated yet'); click(linkDes); advance(fixture); expectPathToBe(location, '/signup'); })); Test Route Worked
  • 184.
    import {advance, expectPathToBe,click } from '../../../testing'; it('all items takes me home', fakeAsync(() => { const linkDes = allLinkDes[4]; expectPathToBe(location, '', 'link should not have navigated yet'); click(linkDes); advance(fixture); expectPathToBe(location, '/signup'); })); Test Route Worked
  • 185.
    import {advance, expectPathToBe,click } from '../../../testing'; it('all items takes me home', fakeAsync(() => { const linkDes = allLinkDes[4]; expectPathToBe(location, '', 'link should not have navigated yet'); click(linkDes); advance(fixture); expectPathToBe(location, '/signup'); })); Test Route Worked
  • 186.
    import {advance, expectPathToBe,click } from '../../../testing'; it('all items takes me home', fakeAsync(() => { const linkDes = allLinkDes[4]; expectPathToBe(location, '', 'link should not have navigated yet'); click(linkDes); advance(fixture); expectPathToBe(location, '/signup'); })); Test Route Worked
  • 187.
    import {advance, expectPathToBe,click } from '../../../testing'; it('all items takes me home', fakeAsync(() => { const linkDes = allLinkDes[4]; expectPathToBe(location, '', 'link should not have navigated yet'); click(linkDes); advance(fixture); expectPathToBe(location, '/signup'); })); Test Route Worked
  • 188.
    export const ButtonClickEvents= { left: { button: 0 }, right: { button: 2 } }; export function click( el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void { if (el instanceof HTMLElement) { el.click(); } else { el.triggerEventHandler('click', eventObj); } } Helper: Click Function
  • 189.
    export const ButtonClickEvents= { left: { button: 0 }, right: { button: 2 } }; export function click( el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void { if (el instanceof HTMLElement) { el.click(); } else { el.triggerEventHandler('click', eventObj); } } Helper: Click Function
  • 190.
    export const ButtonClickEvents= { left: { button: 0 }, right: { button: 2 } }; export function click( el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void { if (el instanceof HTMLElement) { el.click(); } else { el.triggerEventHandler('click', eventObj); } } Helper: Click Function
  • 191.
    export const ButtonClickEvents= { left: { button: 0 }, right: { button: 2 } }; export function click( el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void { if (el instanceof HTMLElement) { el.click(); } else { el.triggerEventHandler('click', eventObj); } } Helper: Click Function
  • 192.
    export function advance(f:ComponentFixture<any>, time?: number): void { if (time) { tick(time); } else { tick(); } f.detectChanges(); } Helper: Advance Function
  • 193.
    export function advance(f:ComponentFixture<any>, time?: number): void { if (time) { tick(time); } else { tick(); } f.detectChanges(); } Helper: Advance Function
  • 194.
    export function advance(f:ComponentFixture<any>, time?: number): void { if (time) { tick(time); } else { tick(); } f.detectChanges(); } Helper: Advance Function
  • 195.
    export function advance(f:ComponentFixture<any>, time?: number): void { if (time) { tick(time); } else { tick(); } f.detectChanges(); } Helper: Advance Function
  • 196.
    import { Location} from '@angular/common'; export function expectPathToBe( l: Location, path: string, expectationFailOutput?: any) { expect(l.path()).toEqual(path, expectationFailOutput || l.path()); } Helper: expectPathToBe function
  • 197.
    import { Location} from '@angular/common'; export function expectPathToBe( l: Location, path: string, expectationFailOutput?: any) { expect(l.path()).toEqual(path, expectationFailOutput || l.path()); } Helper: expectPathToBe function
  • 198.
    import { Location} from '@angular/common'; export function expectPathToBe( l: Location, path: string, expectationFailOutput?: any) { expect(l.path()).toEqual(path, expectationFailOutput || l.path()); } Helper: expectPathToBe function
  • 199.
    import { Location} from '@angular/common'; export function expectPathToBe( l: Location, path: string, expectationFailOutput?: any) { expect(l.path()).toEqual(path, expectationFailOutput || l.path()); } Helper: expectPathToBe function
  • 200.
    digitaldrummerjhttps://digitaldrummerj.me Routing Recap • Don’ttest Router. Test navigation • Use RouterTestingModule • Set routes to same component for testing setup ease
  • 201.
  • 202.
    fdescribe('MyTest Suite', ()=> { fit('My Test', () => { }); }); Run Only This
  • 203.
    fdescribe('MyTest Suite', ()=> { fit('My Test', () => { }); }); Run Only This
  • 204.
    xdescribe('MyTest Suite', ()=> { xit('My Test', () => { }); }); Don't Run This
  • 205.
    xdescribe('MyTest Suite', ()=> { xit('My Test', () => { }); }); Exclude Test
  • 206.
    describe('HeaderComponent', () =>{ setup(); describe('Navigation Test', navigationTests); describe('Create Test', createTest); describe('Toggle Menu Test', toggleMenuTest); }); function setup() { beforeEach(async(() => { })); beforeEach(() => { }); } function navigationTests() { beforeEach(() => { allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); }); } Grouping Tests
  • 207.
    describe('HeaderComponent', () =>{ setup(); describe('Navigation Test', navigationTests); describe('Create Test', createTest); describe('Toggle Menu Test', toggleMenuTest); }); function setup() { beforeEach(async(() => { })); beforeEach(() => { }); } function navigationTests() { beforeEach(() => { allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); }); } Grouping Tests
  • 208.
    describe('HeaderComponent', () =>{ setup(); describe('Navigation Test', navigationTests); describe('Create Test', createTest); describe('Toggle Menu Test', toggleMenuTest); }); function setup() { beforeEach(async(() => { })); beforeEach(() => { }); } function navigationTests() { beforeEach(() => { allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); }); } Grouping Tests
  • 209.
    describe('HeaderComponent', () =>{ setup(); describe('Navigation Test', navigationTests); describe('Create Test', createTest); describe('Toggle Menu Test', toggleMenuTest); }); function setup() { beforeEach(async(() => { })); beforeEach(() => { }); } function navigationTests() { beforeEach(() => { allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); }); } Grouping Tests
  • 210.
    describe('HeaderComponent', () =>{ setup(); describe('Navigation Test', navigationTests); describe('Create Test', createTest); describe('Toggle Menu Test', toggleMenuTest); }); function setup() { beforeEach(async(() => { })); beforeEach(() => { }); } function navigationTests() { beforeEach(() => { allLinkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); }); } Grouping Tests
  • 211.
  • 212.
  • 213.
    digitaldrummerjhttps://digitaldrummerj.me Jasmine Goodies Recap •f = only run this • x = don't run this • Make sure to not check f or x test in • Group like test or test that have expensive setup cost • Additional message doesn’t work for toEqual
  • 214.
    digitaldrummerjhttps://digitaldrummerj.me Unit Tests AreProof That My Code Does What I Think It Does 21
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
    digitaldrummerjhttps://digitaldrummerj.me "Don't let thefear that testing can't catch all bugs stop you from writing the tests that will catch most bugs" Martin Fowler Refactoring 22
  • 226.
    digitaldrummerjhttps://digitaldrummerj.me Resources Unit Testing Guide:angular.io/guide/testing and angular.io/guide/http#testing- http-requests Jasmine Intro: jasmine.github.io/2.0/introduction.html Slides: slideshare.net/digitaldrummerj/ndc-minnesota-2018-ng-unit-testing Code: github.com/digitaldrummerj/angular-tutorial-code/tree/chapter-unit-test 22 https://digitaldrummerj.me /digitaldrummerj
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
    digitaldrummerjhttps://digitaldrummerj.me Component with Inputsand Outputs The goal is to ensure that binding works as expected Test input: simply update value and ensure it renders Test output: trigger triggerEventHandler and subscribe to the EventEmitter 23
  • 234.
    @Component({ selector: 'app-input-output', template: ` <h1>Hello{{subject}}!</h1> <button (click)="depart()">We Out</button> ` }) export class InputOutputComponent { @Input('subject') subject: string; @Output('leave') leave: EventEmitter<string> = new EventEmitter(); depart() { this.leave.emit(`Later ${this.subject}!`); } } Component
  • 235.
    @Component({ selector: 'app-input-output', template: ` <h1>Hello{{subject}}!</h1> <button (click)="depart()">We Out</button> ` }) export class InputOutputComponent { @Input('subject') subject: string; @Output('leave') leave: EventEmitter<string> = new EventEmitter(); depart() { this.leave.emit(`Later ${this.subject}!`); } } Component
  • 236.
    beforeEach(() => { fixture= TestBed.configureTestingModule({ declarations: [ InputOutputComponent ] }) .createComponent(InputOutputComponent); component = fixture.componentInstance; de = fixture.debugElement; button = de.query(By.css('button')); component.subject = 'galaxy'; fixture.detectChanges(); }); Setup
  • 237.
    beforeEach(() => { fixture= TestBed.configureTestingModule({ declarations: [ InputOutputComponent ] }) .createComponent(InputOutputComponent); component = fixture.componentInstance; de = fixture.debugElement; button = de.query(By.css('button')); component.subject = 'galaxy'; fixture.detectChanges(); }); Setup
  • 238.
    beforeEach(() => { fixture= TestBed.configureTestingModule({ declarations: [ InputOutputComponent ] }) .createComponent(InputOutputComponent); component = fixture.componentInstance; de = fixture.debugElement; button = de.query(By.css('button')); component.subject = 'galaxy'; fixture.detectChanges(); }); Setup
  • 239.
    it('has `subject` asan @Input', () => { expect(component.subject).toBe('galaxy'); }); Input
  • 240.
    it('says goodbye tothe `subject`', () => { let farewell; component.leave.subscribe(event => farewell = event); button.triggerEventHandler('click', { button: 0 }); expect(farewell).toBe('Later galaxy!'); }); Output
  • 241.
    it('says goodbye tothe `subject`', () => { let farewell; component.leave.subscribe(event => farewell = event); button.triggerEventHandler('click', { button: 0 }); expect(farewell).toBe('Later galaxy!'); }); Output
  • 242.
    it('says goodbye tothe `subject`', () => { let farewell; component.leave.subscribe(event => farewell = event); button.triggerEventHandler('click', { button: 0 }); expect(farewell).toBe('Later galaxy!'); }); Output
  • 243.
    formErrors = { 'item':'' }; validationMessages = { 'item': { 'required': 'Item is required.', 'minlength': 'Item must be at least 3 characters' } }; Error Messages
  • 244.
    formErrors = { 'item':'' }; validationMessages = { 'item': { 'required': 'Item is required.', 'minlength': 'Item must be at least 3 characters' } }; Error Messages
  • 245.
    onValueChanged(data?: any) { constform = this.addForm; for (const field in this.formErrors) { this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' '; } } } } Check Validation on Field Change
  • 246.
    onValueChanged(data?: any) { constform = this.addForm; for (const field in this.formErrors) { this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' '; } } } } Check Validation on Field Change
  • 247.
    onValueChanged(data?: any) { constform = this.addForm; for (const field in this.formErrors) { this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' '; } } } } Check Validation on Field Change
  • 248.
    onValueChanged(data?: any) { constform = this.addForm; for (const field in this.formErrors) { this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' '; } } } } Check Validation on Field Change
  • 249.
    onValueChanged(data?: any) { constform = this.addForm; for (const field in this.formErrors) { this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += this.formErrors[field] === '' ? messages[key] : messages[key] + ' '; } } } } Check Validation on Field Change
  • 250.
  • 251.
    digitaldrummerjhttps://digitaldrummerj.me Create Fixture Create componentinstance with TestBed.createComponent Returns test fixture Closes TestBed to further configuration 25
  • 252.
    digitaldrummerjhttps://digitaldrummerj.me Configure Test Module TestBedcreates Angular testing module TestBed.configureTestingModule configures module Use BeforeEach to reset module before each test 25
  • 253.
  • 254.
    digitaldrummerjhttps://digitaldrummerj.me Change Detection Fixture.detectChanges triggerschange detection Note: TestBed.createComponent does not trigger 25
  • 255.
    digitaldrummerjhttps://digitaldrummerj.me Component DOM Access DebugElementto access the component's DOM DebugElement.query to query the DOM By.css to query DOM using CSS selectors 25
  • 256.
    digitaldrummerjhttps://digitaldrummerj.me Fast Failures Point to Problems Easyto Read and Write ConsistentIsolatedAutomated Tenants 25