Exploring the validation of a private method call during instantiation
Isolated unit tests are highly recommended for testing a service according to the Angular Testing Guide, as they eliminate the need for Angular testing utilities.
Attempting to spy on an object instance to verify a method call within the constructor is futile since the method has already been executed by the time we obtain a reference to the instance.
Instead, it is necessary to spy on the prototype of the service (credit to Dave Newton!). When defining methods on a JavaScript class
, they are essentially created on the <ClassName>.prototype
.
Utilizing a NgZone spies factory inspired by MockNgZone from Angular testing internals:
import { EventEmitter, NgZone } from '@angular/core';
export function createNgZoneSpy(): NgZone {
const spy = jasmine.createSpyObj('ngZoneSpy', {
onStable: new EventEmitter(false),
run: (fn: Function) => fn(),
runOutsideAngular: (fn: Function) => fn(),
simulateZoneExit: () => { this.onStable.emit(null); },
});
return spy;
}
We can imitate the NgZone dependency to isolate the service in our tests and even characterize the external commands that are executed outside the zone.
// Pure Jasmine - no imports from Angular test libraries
import { NgZone } from '@angular/core';
import { createNgZoneSpy } from '../test/ng-zone-spy';
import { GdlService } from './gdl.service';
describe('GdlService (isolated unit tests)', () => {
describe('loadApp', () => {
const methodUnderTest: string = 'loadApp';
let ngZone: NgZone;
let service: GdlService;
beforeEach(() => {
spyOn<any>(GdlService.prototype, methodUnderTest).and.callThrough();
ngZone = createNgZoneSpy();
service = new GdlService(ngZone);
});
it('initiates loading the app once when instantiated', () => {
expect(GdlService.prototype[methodUnderTest]).toHaveBeenCalledWith(service.appName);
expect(GdlService.prototype[methodUnderTest]).toHaveBeenCalledTimes(1);
});
it('executes logic outside the zone upon instantiation.', () => {
expect(ngZone.runOutsideAngular).toHaveBeenCalledTimes(1);
});
});
});
Typically, testing private methods directly isn't preferred, rather observing the public outcomes they produce.
Nevertheless, Jasmine Spies can be employed to achieve desired results.
View complete example on StackBlitz
The Lifecycle of an Angular Service
Explore illustrations showcasing the Angular Service Lifecycle on StackBlitz. Take note of the comments in the hello.*.ts
files and monitor your JavaScript console for the displayed messages.
Create a Jasmine Test Suite on Angular StackBlitz
Duplicate my StackBlitz to conduct Angular tests using Jasmine similar to what has been discussed above