Software testing
with examples in Angular (and AngularJS)
2018-02-08Paweł Żurowski <zurowski.pawel@gmail.com>
Agenda
• it('should tell about me', ...)
• Experiment
• Discussion
• About testing (in general)
• Best practices
• Live coding: TDD
• How to deal with X?
• QA
Paweł Żurowski <zurowski.pawel@gmail.com> 2018-02-08
it('should tell about me', ...)
• I started programming 19 years ago
• I code for money from the 10 years
• I code for Decerto ~4 years
• I was programming in Pascal, C, PHP, Javascript, Bash, SQL, C++, Java,
Typescript
• I did some AJAX before it was popular
• I did some UX before it was so popular
• I did some Devops before it was so named
• I am doing TDD over the last 5 years (in AngularJS, Angular)
• I regret I have not use TDD before
Paweł Żurowski <zurowski.pawel@gmail.com> 2018-02-08
it('should tell about me', ...)
• I started programming 19 years ago
• I code for money from the 10 years
• I code for Decerto ~4 years
• I was programming in Pascal, C, PHP, Javascript, Bash, SQL, C++, Java,
Typescript
• I did some AJAX before it was popular
• I did some UX before it was so popular
• I did some Devops before it was so named
• I am doing TDD over the last 5 years (in AngularJS, Angular)
• I regret I have not use TDD before
Paweł Żurowski <zurowski.pawel@gmail.com> 2018-02-08
Let’s split up into to teams
• Quiz
• Mark each answer on the scoring sheet
• There is no wrong answer!
• Splitting conditions
•
•
•
•
• Each team designate a leader
Scoring sheet
Question: 1 2 3 4 5 6 7 total
I totally agree
I rather agree
I rather disagree
I totally disagree
I rarely use a debugger
•I totally agree
•I rather agree
•I rather disagree
•I totally disagree
I write more tests LOC than production LOC
•I totally agree
•I rather agree
•I rather disagree
•I totally disagree
I do test getters and setters
•I totally agree
•I rather agree
•I rather disagree
•I totally disagree
I am a professional programmer
•I totally agree
•I rather agree
•I rather disagree
•I totally disagree
I write buggy code
•I totally agree
•I rather agree
•I rather disagree
•I totally disagree
I am not afraid of refactoring
•I totally agree
•I rather agree
•I rather disagree
•I totally disagree
My code has 100% test coverage
•I totally agree
•I rather agree
•I rather disagree
•I totally disagree
Scoring sheet
Question: 1 2 3 4 5 6 7 total
I totally agree
I rather agree
I rather disagree
I totally disagree
Let’s split up into to teams
• Quiz
• Mark each answer on the scoring sheet
• There is no wrong answer!
• Splitting conditions
• Sum up your points - S
• Find the median of all results
• Team A – if score is less then median
• Team B – otherwise
• Each team designate a leader
Scoring sheet
Question: 1 2 3 4 5 6 7 total wage points
I totally agree 2
I rather agree 3
I rather disagree 5
I totally disagree 8
sum
Why are not we testing our own code?
Why are we do testing our own code?
Less then median
otherwise
Why are not we testing our own code?
• It’s too hard
• We don’t know how
• It’s unnecessary
• We don’t have time
• It’s too silly
• We have paid for the code
• It’s only for beginners
• We’ve a deadline
• It’s only for experts
• We don’t want to
• It’s too expensive
• We’re told not to do so
• It’s already working
• We’ve written not testable code
• It’s job for QA
• We’re not told we have to
• It’s boring
• We don’t err
Why are we do testing our own code?
Testing. Big or not?
Testing. Big or not? (from ISTQB Glossary)
acceptance testing, accessibility testing, accuracy testing, ad hoc testing, Agile testing, alpha testing, analytical testing, API
testing, attack-based testing, beta testing, big-bang testing, black-box testing, bottom-up testing, branch testing, business
process-based testing, checklist-based testing, CLI testing, combinatorial testing, compliance testing, component integration
testing, component testing, concurrency testing, condition testing, confirmation testing, consultative testing, control flow
testing, conversion testing, Critical Testing Processes, data flow testing, data-driven testing, database integrity testing,
decision condition testing, decision table testing, decision testing, design-based testing, development testing, discount
usability testing, documentation testing, dynamic testing, efficiency testing, elementary comparison testing, exhaustive
testing, experience-based testing, exploratory testing, factory acceptance testing, failover testing, functional testing,
functionality testing, fuzz testing, GUI testing, hardware-software integration testing, incremental testing, independence of
testing, insourced testing, installability testing, integration testing, interface testing, interoperability testing, invalid testing,
isolation testing, keyword-driven testing, load testing, load testing tool, maintainability testing, maintenance testing,
methodical testing, model-based testing, modified condition / decision testing, monkey testing, multiple condition testing,
mutation testing, N-switch testing, n-wise testing, negative testing, neighborhood integration testing, non-functional testing,
operational acceptance testing, operational profile testing, operational testing, orthogonal array testing, outsourced testing,
pair testing, pairwise integration testing, pairwise testing, path testing, penetration testing, performance testing,
performance testing tool, portability testing, procedure testing, process-compliant testing, random testing, reactive testing,
recoverability testing, regression testing, regression-averse testing, reliability testing, requirements-based testing, resource
utilization testing, risk-based testing, robustness testing, safety testing, scalability testing, scripted testing, security testing,
security testing tool, session-based testing, site acceptance testing, standard-compliant testing, state transition testing,
statement testing, static testing, statistical testing, stress testing, stress testing tool, suitability testing, syntax testing, system
integration testing, system testing, think aloud usability testing
Test type
UI
tests
Integration
tests
Unit tests
https://www.slideshare.net/erdemcp/endtoend-test-automation-for-both-horizontal-and-vertical-scale/19
High
Medium
Low
Long/High
Medium
Short/Low
Low
Medium
High
Business Logic
Coverage
Code
Coverage
Execution
Time/Costs
Test type
manual
tests
E2E tests
Tests with
testbed
Tests without
testbed
High
Medium
Low
Long/High
Medium
Short/Low
Low
Medium
High
Business Logic
Coverage
Code
Coverage
Execution
Time/Costs
AAA ?
AAA ??
Image source: https://commons.wikimedia.org/wiki/File:Three_AAA_Batteries_and_one_AA_Battery.JPG
AAA
• Arrange
• Act
• Assert
AAA
• Arrange
• Act
• Assert
• Given
• When
• Then
AAA
• Arrange
• Act
• Assert
describe('namesReducer', () => {
it('should sort', () => {
const inputState = {data: ['dd', 'aa', 'cc', 'bb']};
const state = namesReducer(inputState, {type: 'SORT'});
expect(state.data).toEqual(['aa', 'bb', 'cc', 'dd',]);
})
});
Best practices – AAA
• Why?
• Each stage has been separated
• It’s easy to add a trigger just after
the act statement
• You have 100% certainty that
assertion have been called
• Easy to spot preparation and
expectations
it('dont', () => {
something.method()
.then(result =>
expect(result).toEqual(42))
});
it('do',()=>{
let result = null;
something.method()
.then(aResult => result = aResult);
expect(result).toEqual(42);
});
Best practices – Less DRY
• Why?
• Too much DRY is consider harmful
• You are immune to nasty bugs
• act(x){ x.splice(0); return x; }
• You can still DRY with
beforeEach sections
it('dont', () => {
const DATA = [1, 1, 2, 3, 5, 8, 13];
const result = something.act(DATA);
expect(result).toEqual(DATA);
});
it('do',()=>{
const result = something.act(
[1, 1, 2, 3, 5, 8, 13]);
expect(result).toEqual(
[1, 1, 2, 3, 5, 8, 13]);
});
Best practices – check one thing at a time
• Why?
• If something blow up you will be
know what exactly
• There are no preconditions,
there are postcondition of
starting situations
• You can still use many
expectations as long as you
check the same one thing
it('dont', () => {
expect(something.here).toEqual(42);
something.act();
expect(something.here).toEqual(24);
});
it('do 1',()=>{
expect(something.here).toEqual(42);
});
it('do 2',()=>{
something.act();
expect(something.here).toEqual(24);
});
Best practices – group by common case
• Why?
• Easier to reason about
• Easier to write
• It looks like documentation
describe('stack',()=>{
describe('when empty',()=>{
it('should fail on pop');
it('should push element on top');
it('should have no elements');
});
describe('with one element',()=>{
it('should pop the element');
it('should push element on top');
it('should have 1 element');
});
describe('when full',()=>{
it('should pop the element');
it('should fail on push');
it('should have SIZE elements');
});
});
Best practices – Test Driven Development
Uncle Bob
Rules:
• You are not allowed to write any
production code unless it is to make a
failing unit test pass.
• You are not allowed to write any
more of a unit test than is sufficient
to fail; and compilation failures are
failures.
• You are not allowed to write any
more production code than is
sufficient to pass the one failing unit
test.
Kent Beck
Rules:
• Don’t write a line of new code unless you
first have a failing automated test.
• Eliminate duplication
Tasks:
• Red—write a little test that doesn’t work,
perhaps doesn’t even compile at first
• Green—make the test work quickly,
committing whatever sins necessary in
the process
• Refactor—eliminate all the duplication
created in just getting the test to work
Best practices – Test Driven Development
Uncle Bob
(…) think about what would happen if
you walked in a room full of people
working this way. Pick any random
person at any random time. A minute
ago, all their code worked.
Let me repeat that: A minute ago all
their code worked! And it doesn't
matter who you pick, and it doesn't
matter when you pick. A minute ago all
their code worked!
Kent Beck
The general TDD cycle goes as follows.
1. Write a test…
2. Make it run…
3. Make it right…
The goal is clean code that works
(thanks to Ron Jeffries for this pithy
summary)… First we’ll solve the “that
works” part of the problem. Then we’ll
solve the “clean code” part.
Live coding – TDD
How to deal with services?
spyOn(SomeResource, 'save')
.and.returnValue({$promise: $q.when({})});
$cookies = jasmine.createSpyObj('$cookies', ['put', 'get']);
someServiceMock = jasmine.createSpyObj(‚someService',
Object.keys(SomeService.prototype));
How to deal with components?
beforeEach(inject(function ($componentController) {
ctrl = $componentController('aComponent',
{
overrideDI: something
},
{
binding: 42
});
ctrl.$onInit();
}));
afterEach(function () {
ctrl.$onDestroy();
});
How to deal with date?
beforeEach(() => {
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should do something on leap day', () => {
jasmine.clock().mockDate(new Date(2020, 1, 29));
const result = something.doIt();
expect(result).toEqual(42);
});
How to deal with global object? (1)
describe('TheService', () => {
let service: RoutingService;
// ...
it('should navigate outside the app', () => {
service.openExternal('www.decerto.pl');
expect(window.open).toHaveBeenCalledWith('https://www.decerto.pl');
});
});
How to deal with global object? (2)
describe('TheService', () => {
let service: TheService;
let window: Window;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [TheService,
{provide: windowToken, useValue: {open: createSpy('open')}}
],
});
service = TestBed.get(TheService);
window = TestBed.get(windowToken);
});
// ...
});
How to deal with global object? (3)
export const windowToken = new InjectionToken<Window>('Window token');
// ------------------------------------------------
@Injectable()
export class TheService {
constructor(@Inject(windowToken) private window: Window) {
}
openExternal(url: string) {
this.window.open(`https://${url}`);
}
}
{provide: windowToken, useValue: window}
How to deal with data service? (1)
@Injectable()
export abstract class SthDataService {
abstract create(): Observable<Sth>;
abstract fetch(sthId: string): Observable<Sth>;
}
@Injectable()
export class SthDataServiceImpl implements SthDataService {
constructor(private http: HttpClient,
@Inject(apiUrlToken) private api: string) {
}
create(): Observable<Sth> {
return this.http.post<Sth>(`${this.api}sth`, {x: 42});
}
fetch(sthId: string): Observable<Sth> {
return this.http.get<Sth>(`${this.api}sth/${sthId}`);
}
}
How to deal with data service? (2)
describe('SthDataService', () => {
let service: SthDataService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
{provice: SthDataService, useClass: SthDataServiceImpl},
{provide: apiUrlToken, useValue: API},
]
});
service = TestBed.get(SthDataService);
httpMock = TestBed.get(HttpTestingController);
});
How to deal with data service? (3)
afterEach(() => httpMock.verify());
it('should fetch sth', () => {
const result = [];
service.fetch('xyz').subscribe(item => result.push(item));
const req = httpMock.expectOne(`${API}sth/xyz`);
req.flush(sampleSthById('xyz'));
expect(req.request.method).toBe('GET');
expect(req.request.url).toBe(`${API}sth/xyz`);
expect(result).toEqual([
sampleSthById('xyz'),
]);
});
How to deal with data service? (4)
@Injectable()
export class SthDataServiceMock implements SthDataService {
constructor(@Optional() private testScheduler: Scheduler,
@Inject(apiMockDelayToken) private delayMs: DelayMs) {}
private get scheduler() {
return this.testScheduler ? this.testScheduler :
/* istanbul ignore next: Scheduler to use when mocking
backend outside the tests */ async;
}
create(): Observable<Sth> {
return this.toDelayedObservable(sampleSthById('new'));
}
fetch(sthId: string): Observable<Sth> {
return this.toDelayedObservable(sampleSthById(sthId));
}
How to deal with data service? (5)
private toDelayedObservable<T>(value: T): Observable<T> {
return interval(this.delayMs(), this.scheduler).pipe(
take(1),
mapTo(value),
);
}
export type DelayMs = () => number;
export const apiUrlToken = new InjectionToken<string>('Api url prefix');
export const apiMockDelayToken = new InjectionToken<DelayMs>('Delay fn');
export const apiMockDelayProvider = (min = 500, max = 2000): Provider[] => [
{
provide: apiMockDelayToken,
useValue: () => min + Math.round(Math.random() * (max - min))
}];
How to deal with observables?
it('should fetch document when requested one mismatch actual', () => {
(serviceMock.selectDoc as Spy)
.and.returnValue(new BehaviorSubject({id: 'doc'}));
const actions = new Actions(hot('--a----b----b-c---', {
a: {type: ActionTypes.REQUEST_DOCUMENT, docId: 'doc'},
b: {type: ActionTypes.REQUEST_DOCUMENT, docId: 'sth'},
c: {type: ActionTypes.REQUEST_DOCUMENT, docId: 'other'},
}));
const effects = createEffects(actions);
expect(effects.fetch$).toBeObservable(hot('----------B------C-', {
B: {type: ActionTypes.DOCUMENT, doc: sampleDocById('sth')},
C: {type: ActionTypes.DOCUMENT, doc: sampleDocById('other')},
}));
});
How to deal with effects? (without Testbed)
let storeMock: Store<{ something: SomethingState }>;
let stateSubject: BehaviorSubject<{ something: SomethingState }>;
function createEffects(actions: Actions<Action>) {
return new SomethingEffects(actions,
new SomethingService(storeMock),
new SomethingDataServiceMock(getTestScheduler(), () => 30));
}
beforeEach(() => {
stateSubject = new BehaviorSubject({});
storeMock = this.stateSubject.asObservable();
});
Q&A.

Software testing with examples in Angular (and AngularJS)

  • 1.
    Software testing with examplesin Angular (and AngularJS) 2018-02-08Paweł Żurowski <zurowski.pawel@gmail.com>
  • 2.
    Agenda • it('should tellabout me', ...) • Experiment • Discussion • About testing (in general) • Best practices • Live coding: TDD • How to deal with X? • QA Paweł Żurowski <zurowski.pawel@gmail.com> 2018-02-08
  • 3.
    it('should tell aboutme', ...) • I started programming 19 years ago • I code for money from the 10 years • I code for Decerto ~4 years • I was programming in Pascal, C, PHP, Javascript, Bash, SQL, C++, Java, Typescript • I did some AJAX before it was popular • I did some UX before it was so popular • I did some Devops before it was so named • I am doing TDD over the last 5 years (in AngularJS, Angular) • I regret I have not use TDD before Paweł Żurowski <zurowski.pawel@gmail.com> 2018-02-08
  • 4.
    it('should tell aboutme', ...) • I started programming 19 years ago • I code for money from the 10 years • I code for Decerto ~4 years • I was programming in Pascal, C, PHP, Javascript, Bash, SQL, C++, Java, Typescript • I did some AJAX before it was popular • I did some UX before it was so popular • I did some Devops before it was so named • I am doing TDD over the last 5 years (in AngularJS, Angular) • I regret I have not use TDD before Paweł Żurowski <zurowski.pawel@gmail.com> 2018-02-08
  • 5.
    Let’s split upinto to teams • Quiz • Mark each answer on the scoring sheet • There is no wrong answer! • Splitting conditions • • • • • Each team designate a leader
  • 6.
    Scoring sheet Question: 12 3 4 5 6 7 total I totally agree I rather agree I rather disagree I totally disagree
  • 7.
    I rarely usea debugger •I totally agree •I rather agree •I rather disagree •I totally disagree
  • 8.
    I write moretests LOC than production LOC •I totally agree •I rather agree •I rather disagree •I totally disagree
  • 9.
    I do testgetters and setters •I totally agree •I rather agree •I rather disagree •I totally disagree
  • 10.
    I am aprofessional programmer •I totally agree •I rather agree •I rather disagree •I totally disagree
  • 11.
    I write buggycode •I totally agree •I rather agree •I rather disagree •I totally disagree
  • 12.
    I am notafraid of refactoring •I totally agree •I rather agree •I rather disagree •I totally disagree
  • 13.
    My code has100% test coverage •I totally agree •I rather agree •I rather disagree •I totally disagree
  • 14.
    Scoring sheet Question: 12 3 4 5 6 7 total I totally agree I rather agree I rather disagree I totally disagree
  • 15.
    Let’s split upinto to teams • Quiz • Mark each answer on the scoring sheet • There is no wrong answer! • Splitting conditions • Sum up your points - S • Find the median of all results • Team A – if score is less then median • Team B – otherwise • Each team designate a leader
  • 16.
    Scoring sheet Question: 12 3 4 5 6 7 total wage points I totally agree 2 I rather agree 3 I rather disagree 5 I totally disagree 8 sum
  • 17.
    Why are notwe testing our own code? Why are we do testing our own code? Less then median otherwise
  • 18.
    Why are notwe testing our own code? • It’s too hard • We don’t know how • It’s unnecessary • We don’t have time • It’s too silly • We have paid for the code • It’s only for beginners • We’ve a deadline • It’s only for experts • We don’t want to • It’s too expensive • We’re told not to do so • It’s already working • We’ve written not testable code • It’s job for QA • We’re not told we have to • It’s boring • We don’t err
  • 19.
    Why are wedo testing our own code?
  • 20.
  • 21.
    Testing. Big ornot? (from ISTQB Glossary) acceptance testing, accessibility testing, accuracy testing, ad hoc testing, Agile testing, alpha testing, analytical testing, API testing, attack-based testing, beta testing, big-bang testing, black-box testing, bottom-up testing, branch testing, business process-based testing, checklist-based testing, CLI testing, combinatorial testing, compliance testing, component integration testing, component testing, concurrency testing, condition testing, confirmation testing, consultative testing, control flow testing, conversion testing, Critical Testing Processes, data flow testing, data-driven testing, database integrity testing, decision condition testing, decision table testing, decision testing, design-based testing, development testing, discount usability testing, documentation testing, dynamic testing, efficiency testing, elementary comparison testing, exhaustive testing, experience-based testing, exploratory testing, factory acceptance testing, failover testing, functional testing, functionality testing, fuzz testing, GUI testing, hardware-software integration testing, incremental testing, independence of testing, insourced testing, installability testing, integration testing, interface testing, interoperability testing, invalid testing, isolation testing, keyword-driven testing, load testing, load testing tool, maintainability testing, maintenance testing, methodical testing, model-based testing, modified condition / decision testing, monkey testing, multiple condition testing, mutation testing, N-switch testing, n-wise testing, negative testing, neighborhood integration testing, non-functional testing, operational acceptance testing, operational profile testing, operational testing, orthogonal array testing, outsourced testing, pair testing, pairwise integration testing, pairwise testing, path testing, penetration testing, performance testing, performance testing tool, portability testing, procedure testing, process-compliant testing, random testing, reactive testing, recoverability testing, regression testing, regression-averse testing, reliability testing, requirements-based testing, resource utilization testing, risk-based testing, robustness testing, safety testing, scalability testing, scripted testing, security testing, security testing tool, session-based testing, site acceptance testing, standard-compliant testing, state transition testing, statement testing, static testing, statistical testing, stress testing, stress testing tool, suitability testing, syntax testing, system integration testing, system testing, think aloud usability testing
  • 22.
  • 23.
    Test type manual tests E2E tests Testswith testbed Tests without testbed High Medium Low Long/High Medium Short/Low Low Medium High Business Logic Coverage Code Coverage Execution Time/Costs
  • 24.
  • 25.
    AAA ?? Image source:https://commons.wikimedia.org/wiki/File:Three_AAA_Batteries_and_one_AA_Battery.JPG
  • 26.
  • 27.
    AAA • Arrange • Act •Assert • Given • When • Then
  • 28.
    AAA • Arrange • Act •Assert describe('namesReducer', () => { it('should sort', () => { const inputState = {data: ['dd', 'aa', 'cc', 'bb']}; const state = namesReducer(inputState, {type: 'SORT'}); expect(state.data).toEqual(['aa', 'bb', 'cc', 'dd',]); }) });
  • 29.
    Best practices –AAA • Why? • Each stage has been separated • It’s easy to add a trigger just after the act statement • You have 100% certainty that assertion have been called • Easy to spot preparation and expectations it('dont', () => { something.method() .then(result => expect(result).toEqual(42)) }); it('do',()=>{ let result = null; something.method() .then(aResult => result = aResult); expect(result).toEqual(42); });
  • 30.
    Best practices –Less DRY • Why? • Too much DRY is consider harmful • You are immune to nasty bugs • act(x){ x.splice(0); return x; } • You can still DRY with beforeEach sections it('dont', () => { const DATA = [1, 1, 2, 3, 5, 8, 13]; const result = something.act(DATA); expect(result).toEqual(DATA); }); it('do',()=>{ const result = something.act( [1, 1, 2, 3, 5, 8, 13]); expect(result).toEqual( [1, 1, 2, 3, 5, 8, 13]); });
  • 31.
    Best practices –check one thing at a time • Why? • If something blow up you will be know what exactly • There are no preconditions, there are postcondition of starting situations • You can still use many expectations as long as you check the same one thing it('dont', () => { expect(something.here).toEqual(42); something.act(); expect(something.here).toEqual(24); }); it('do 1',()=>{ expect(something.here).toEqual(42); }); it('do 2',()=>{ something.act(); expect(something.here).toEqual(24); });
  • 32.
    Best practices –group by common case • Why? • Easier to reason about • Easier to write • It looks like documentation describe('stack',()=>{ describe('when empty',()=>{ it('should fail on pop'); it('should push element on top'); it('should have no elements'); }); describe('with one element',()=>{ it('should pop the element'); it('should push element on top'); it('should have 1 element'); }); describe('when full',()=>{ it('should pop the element'); it('should fail on push'); it('should have SIZE elements'); }); });
  • 33.
    Best practices –Test Driven Development Uncle Bob Rules: • You are not allowed to write any production code unless it is to make a failing unit test pass. • You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures. • You are not allowed to write any more production code than is sufficient to pass the one failing unit test. Kent Beck Rules: • Don’t write a line of new code unless you first have a failing automated test. • Eliminate duplication Tasks: • Red—write a little test that doesn’t work, perhaps doesn’t even compile at first • Green—make the test work quickly, committing whatever sins necessary in the process • Refactor—eliminate all the duplication created in just getting the test to work
  • 34.
    Best practices –Test Driven Development Uncle Bob (…) think about what would happen if you walked in a room full of people working this way. Pick any random person at any random time. A minute ago, all their code worked. Let me repeat that: A minute ago all their code worked! And it doesn't matter who you pick, and it doesn't matter when you pick. A minute ago all their code worked! Kent Beck The general TDD cycle goes as follows. 1. Write a test… 2. Make it run… 3. Make it right… The goal is clean code that works (thanks to Ron Jeffries for this pithy summary)… First we’ll solve the “that works” part of the problem. Then we’ll solve the “clean code” part.
  • 35.
  • 36.
    How to dealwith services? spyOn(SomeResource, 'save') .and.returnValue({$promise: $q.when({})}); $cookies = jasmine.createSpyObj('$cookies', ['put', 'get']); someServiceMock = jasmine.createSpyObj(‚someService', Object.keys(SomeService.prototype));
  • 37.
    How to dealwith components? beforeEach(inject(function ($componentController) { ctrl = $componentController('aComponent', { overrideDI: something }, { binding: 42 }); ctrl.$onInit(); })); afterEach(function () { ctrl.$onDestroy(); });
  • 38.
    How to dealwith date? beforeEach(() => { jasmine.clock().install(); }); afterEach(() => { jasmine.clock().uninstall(); }); it('should do something on leap day', () => { jasmine.clock().mockDate(new Date(2020, 1, 29)); const result = something.doIt(); expect(result).toEqual(42); });
  • 39.
    How to dealwith global object? (1) describe('TheService', () => { let service: RoutingService; // ... it('should navigate outside the app', () => { service.openExternal('www.decerto.pl'); expect(window.open).toHaveBeenCalledWith('https://www.decerto.pl'); }); });
  • 40.
    How to dealwith global object? (2) describe('TheService', () => { let service: TheService; let window: Window; beforeEach(() => { TestBed.configureTestingModule({ providers: [TheService, {provide: windowToken, useValue: {open: createSpy('open')}} ], }); service = TestBed.get(TheService); window = TestBed.get(windowToken); }); // ... });
  • 41.
    How to dealwith global object? (3) export const windowToken = new InjectionToken<Window>('Window token'); // ------------------------------------------------ @Injectable() export class TheService { constructor(@Inject(windowToken) private window: Window) { } openExternal(url: string) { this.window.open(`https://${url}`); } } {provide: windowToken, useValue: window}
  • 42.
    How to dealwith data service? (1) @Injectable() export abstract class SthDataService { abstract create(): Observable<Sth>; abstract fetch(sthId: string): Observable<Sth>; } @Injectable() export class SthDataServiceImpl implements SthDataService { constructor(private http: HttpClient, @Inject(apiUrlToken) private api: string) { } create(): Observable<Sth> { return this.http.post<Sth>(`${this.api}sth`, {x: 42}); } fetch(sthId: string): Observable<Sth> { return this.http.get<Sth>(`${this.api}sth/${sthId}`); } }
  • 43.
    How to dealwith data service? (2) describe('SthDataService', () => { let service: SthDataService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ {provice: SthDataService, useClass: SthDataServiceImpl}, {provide: apiUrlToken, useValue: API}, ] }); service = TestBed.get(SthDataService); httpMock = TestBed.get(HttpTestingController); });
  • 44.
    How to dealwith data service? (3) afterEach(() => httpMock.verify()); it('should fetch sth', () => { const result = []; service.fetch('xyz').subscribe(item => result.push(item)); const req = httpMock.expectOne(`${API}sth/xyz`); req.flush(sampleSthById('xyz')); expect(req.request.method).toBe('GET'); expect(req.request.url).toBe(`${API}sth/xyz`); expect(result).toEqual([ sampleSthById('xyz'), ]); });
  • 45.
    How to dealwith data service? (4) @Injectable() export class SthDataServiceMock implements SthDataService { constructor(@Optional() private testScheduler: Scheduler, @Inject(apiMockDelayToken) private delayMs: DelayMs) {} private get scheduler() { return this.testScheduler ? this.testScheduler : /* istanbul ignore next: Scheduler to use when mocking backend outside the tests */ async; } create(): Observable<Sth> { return this.toDelayedObservable(sampleSthById('new')); } fetch(sthId: string): Observable<Sth> { return this.toDelayedObservable(sampleSthById(sthId)); }
  • 46.
    How to dealwith data service? (5) private toDelayedObservable<T>(value: T): Observable<T> { return interval(this.delayMs(), this.scheduler).pipe( take(1), mapTo(value), ); } export type DelayMs = () => number; export const apiUrlToken = new InjectionToken<string>('Api url prefix'); export const apiMockDelayToken = new InjectionToken<DelayMs>('Delay fn'); export const apiMockDelayProvider = (min = 500, max = 2000): Provider[] => [ { provide: apiMockDelayToken, useValue: () => min + Math.round(Math.random() * (max - min)) }];
  • 47.
    How to dealwith observables? it('should fetch document when requested one mismatch actual', () => { (serviceMock.selectDoc as Spy) .and.returnValue(new BehaviorSubject({id: 'doc'})); const actions = new Actions(hot('--a----b----b-c---', { a: {type: ActionTypes.REQUEST_DOCUMENT, docId: 'doc'}, b: {type: ActionTypes.REQUEST_DOCUMENT, docId: 'sth'}, c: {type: ActionTypes.REQUEST_DOCUMENT, docId: 'other'}, })); const effects = createEffects(actions); expect(effects.fetch$).toBeObservable(hot('----------B------C-', { B: {type: ActionTypes.DOCUMENT, doc: sampleDocById('sth')}, C: {type: ActionTypes.DOCUMENT, doc: sampleDocById('other')}, })); });
  • 48.
    How to dealwith effects? (without Testbed) let storeMock: Store<{ something: SomethingState }>; let stateSubject: BehaviorSubject<{ something: SomethingState }>; function createEffects(actions: Actions<Action>) { return new SomethingEffects(actions, new SomethingService(storeMock), new SomethingDataServiceMock(getTestScheduler(), () => 30)); } beforeEach(() => { stateSubject = new BehaviorSubject({}); storeMock = this.stateSubject.asObservable(); });
  • 50.