slides.com/gerardsans | @gerardsans
Testing Angular Applications 4+
Spoken at 53 events in 18 countries
900
1K
Angular In Flip Flops
Unit Tests
Assertion Libraries
Spies, Stubs
Test
Automation
Browsers
Coverage Reports
e2e Tests
Test
Runner
WebDriverJS
Selenium
Protractor
Unit tests
e2e Tests
Acceptance Tests
$ npm run tests $ npm run e2e
let calculator = {
add: (a, b) => a + b
};
describe('Calculator', () => {
it('should add two numbers', () => {
expect(calculator.add(1,1)).toBe(2);
})
})
fail(msg), pending(msg)
xdescribe, xit
fdescribe, fit
Test double functions that record calls, arguments and return values
describe('Spies', () => {
let calculator = { add: (a,b) => a+b };
it('should track calls but NOT call through', () => {
spyOn(calculator, 'add');
let result = calculator.add(1,1);
expect(calculator.add).toHaveBeenCalled();
expect(calculator.add).toHaveBeenCalledTimes(1);
expect(calculator.add).toHaveBeenCalledWith(1,1);
expect(result).not.toEqual(2);
})
})
describe('Spies', () => {
it('should call through', () => {
spyOn(calculator, 'add').and.callThrough();
let result = calculator.add(1,1);
expect(result).toEqual(2);
//restore stub behaviour
calculator.add.and.stub();
expect(calculator.add(1,1)).not.toEqual(2);
})
})
describe('Spies', () => {
it('should return value with 42', () => {
spyOn(calculator, 'add').and.returnValue(42);
let result = calculator.add(1,1);
expect(result).toEqual(42);
})
it('should return values 1, 2, 3', () => {
spyOn(calculator, 'add').and.returnValues(1, 2, 3);
expect(calculator.add(1,1)).toEqual(1);
expect(calculator.add(1,1)).toEqual(2);
expect(calculator.add(1,1)).toEqual(3);
})
})
describe('Spies', () => {
it('should call fake function returning 42', () => {
spyOn(calculator, 'add').and.callFake((a,b) => 42);
expect(calculator.add(1,1)).toEqual(42);
})
})
describe('Spies', () => {
it('should throw with error', () => {
spyOn(calculator, 'add').and.throwError("Ups");
expect(() => calculator.add(1,1)).toThrowError("Ups");
})
})
describe('Spies', () => {
it('should be able to create a spy manually', () => {
let add = jasmine.createSpy('add');
add();
expect(add).toHaveBeenCalled();
})
})
// usage: create spy to use as a callback
// setTimeout(add, 100);
describe('Spies', () => {
it('should be able to create multiple spies manually', () => {
let calculator = jasmine.createSpyObj('calculator', ['add']);
calculator.add.and.returnValue(42);
let result = calculator.add(1,1);
expect(calculator.add).toHaveBeenCalled();
expect(result).toEqual(42);
})
})
import { TestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
import {Injectable} from '@angular/core';
@Injectable()
export class LanguagesService {
get() {
return ['en', 'es', 'fr'];
}
}
describe('Service: LanguagesService', () => {
//setup
beforeEach(() => TestBed.configureTestingModule({
providers: [ LanguagesService ]
}));
//specs
it('should return available languages', inject([LanguagesService], service => {
let languages = service.get();
expect(languages).toContain('en');
expect(languages).toContain('es');
expect(languages).toContain('fr');
expect(languages.length).toEqual(3);
});
});
describe('Service: LanguagesService', () => {
let service;
beforeEach(() => TestBed.configureTestingModule({
providers: [ LanguagesService ]
}));
beforeEach(inject([LanguagesService], s => {
service = s;
}));
it('should return available languages', () => {
let languages = service.get();
expect(languages).toContain('en');
expect(languages).toContain('es');
expect(languages).toContain('fr');
expect(languages.length).toEqual(3);
});
});
describe('Service: LanguagesService', () => {
let service;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ LanguagesService ]
})}
service = TestBed.get(LanguagesService);
);
it('should return available languages', () => {
let languages = service.get();
expect(languages).toContain('en');
expect(languages).toContain('es');
expect(languages).toContain('fr');
expect(languages.length).toEqual(3);
});
});
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import 'rxjs/add/operator/map';
@Injectable()
export class UsersService {
constructor(private http: HttpClient) { }
public get() {
return this.http.get('./src/assets/users.json')
.map(response => response.users);
}
}
describe('Service: UsersService', () => {
let service, http;
beforeEach(() => TestBed.configureTestingModule({
imports: [ HttpClientModule ],
providers: [ UsersService ]
}));
beforeEach(inject([UsersService, HttpClient], (s, h) => {
service = s;
http = h;
}));
[...]
describe('Service: UsersService', () => {
[...]
it('should return available users (LIVE)', done => {
service.get()
.subscribe({
next: res => {
expect(res.users).toBe(USERS);
expect(res.users.length).toEqual(2);
done();
}
});
});
});
describe('Service: UsersService', () => {
let service, httpMock;
beforeEach(() => TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ],
providers: [ UsersService ]
}));
beforeEach(inject([UsersService, HttpTestingController], (s, h) => {
service = s;
httpMock = h;
}));
afterEach(httpMock.verify);
[...]
describe('Service: UsersService', () => {
[...]
it('should return available users', done => {
service.get()
.subscribe({
next: res => {
expect(res.users).toBe(USERS);
expect(res.users.length).toEqual(2);
done();
}
});
httpMock.expectOne('./src/assets/users.json')
.flush(USERS);
});
});
import {Component, Input} from '@angular/core';
@Component({
selector: 'greeter', // <greeter name="Igor"></greeter>
template: `<h1>Hello {{name}}!</h1>`
})
export class Greeter {
@Input() name;
}
describe('Component: Greeter', () => {
let fixture, greeter, element, de;
//setup
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ Greeter ]
});
fixture = TestBed.createComponent(Greeter);
greeter = fixture.componentInstance;
element = fixture.nativeElement;
de = fixture.debugElement;
});
}
describe('Component: Greeter', () => {
let fixture, greeter, element, de;
//setup
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ Greeter ],
})
.compileComponents() // compile external templates and css
.then(() => {
fixture = TestBed.createComponent(Greeter);
greeter = fixture.componentInstance;
element = fixture.nativeElement;
de = fixture.debugElement;
});
))
});
describe('Component: Greeter', () => {
it('should render `Hello World!`', async(() => {
greeter.name = 'World';
//trigger change detection
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(element.querySelector('h1').innerText).toBe('Hello World!');
});
}));
}
TestBed.get(AuthService)
de.injector.get(AuthService)
@NgModule({providers:[]})
@Component({providers:[]})
/* <greeter name="World">
<div highlight>
<h1>Hello World!</h1>
</div>
</greeter> */
// single match: query
expect(de.query(By.all()).nativeElement.innerText).toBe('Hello World!');
expect(de.query(By.css('h1')).nativeElement.innerText).toBe('Hello World!');
expect(de.query(By.directive(Highlight)).nativeElement.innerText).toBe('Hello World!');
expect(de.query(By.directive(Highlight)).componentInstance.name).toBe('World');
// multiple match: queryAll
de.queryAll(By.all()).forEach(node => {
if (node.nativeElement.matches('h1')) {
expect(node.nativeElement.innerText).toBe('Hello World!');
}
});
describe('Component: Greeter', () => {
it('should render `Hello World!`', fakeAsync(() => {
greeter.name = 'World';
//trigger change detection
fixture.detectChanges();
//execute all pending asynchronous calls
tick();
expect(element.querySelector('h1').innerText).toBe('Hello World!');
expect(de.query(By.css('h1')).nativeElement.innerText).toBe('Hello World!');
}));
}
describe('Component: Greeter', () => {
let fixture, greeter, element, de;
//setup
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ Greeter ],
})
.compileComponents() // compile external templates and css
.then(() => {
TestBed.overrideTemplate(Greeter, '<h1>Hi</h1>');
fixture = TestBed.createComponent(Greeter);
greeter = fixture.componentInstance;
element = fixture.nativeElement;
de = fixture.debugElement;
});
))
});
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent ],
schemas: [ NO_ERRORS_SCHEMA ]
})
});
// Error: Timed out waiting for page to load after 10000ms
getPageTimeout: NEW_TIMEOUT_MS
browser.get(url, NEW_TIMEOUT_MS)
// Timed out waiting for asynchronous Angular tasks
// to finish after 11 seconds.
allScriptsTimeout: NEW_TIMEOUT_MS
this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
// Changes here will not propagate into your view.
this.ngZone.run(() => {
// Run inside the ngZone to trigger change detection.
});
}, REALLY_LONG_DELAY);
});
browser.waitForAngularEnabled(false);
browser.get('/non-angular-page.html');
browser.waitForAngularEnabled(true);
browser.get('/angular-page.html');
// timeout: timed out waiting for spec to complete
jasmineNodeOpts: {
defaultTimeoutInterval: NEW_TIMEOUT_MS
}
// it(title, fn, timeout)
it('should work with long timeout', () => {
service.isOnline().then(online => {
expect(online).toBe(true)
})
}, NEW_TIMEOUT_MS)
Components, Directives, Pipes
Services, Http, MockBackend
Router, Observables
Spies