Verify that the Angular service has been initialized

I am trying to test my Angular service using Karma-Jasmine, and I have to be sure that the function was called after the loadApp service was initialized. What is the best way to test it?

 import { Injectable, NgZone } from '@angular/core'; @Injectable() export class GdlService { appName = 'myAppName'; constructor( private ngZone: NgZone, ) { this.ngZone = ngZone; this.loadApp(this.appName); } private loadApp(appName) { this.ngZone.runOutsideAngular(() => { // ...some logic }); } } 
+5
source share
4 answers

It can be tested like any other function. Given that loadApp is a prototype method, it can be truncated or traced on a prototype class:

 it('', () => { spyOn(<any>GdlService.prototype, 'loadApp'); const gdl = TestBed.get(GdlService); expect(gdl['loadApp']).toHaveBeenCalledWith('myAppName'); }); 
+1
source

Try mocking the injection for ngZone (I like ts-mockito for this kind of thing) and then checking to see if ngZone.outsideOfAngular has been called. Due to the nature of typescript, I don’t think you can directly monitor something that’s convenient.

Something like this in the test file:

 import { GdlService } from 'place'; import { NgZone } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { anything, instance, mock, verify } from 'ts-mockito'; describe('yada yada', () => { const mockNgZone = mock(NgZone); // Can use when(mockNgZone.whatever)... to mock what you need beforeEach(() => { TestBed.configureTestModule({ providers: [{ provide: NgZone, useValue: instance(mockNgZone) }] }); }); it('checks on loadApp', () => { verify(mockNgZone.runOutsideAngular(anything())).called(); }); }); 

If you prefer to use the spyOn method instead, you can simply replace the object in the useValue section of the provider.

+3
source

Increasing participant visibility for testing purposes is in order. So for the sake of elegance, you can make loadApp publicly available for ridicule. However, trying to mock a private function will have some compromise. @estus is on the right track, answering it:

I modified it a bit to change the prototype using jasmine.createSpy to overwrite a private function.

  it('try to call loadApp', () => { GdlService.prototype['loadApp'] = jasmine.createSpy() .and .callFake((appName) => { console.log('loadApp called with ' , appName ); }); // spyOn(IEFUserService.prototype, 'loadAppPrivate'); - this does not work because the test breaks right here trying to access private member const service = TestBed.get(GdlService); expect(service['loadApp']).toHaveBeenCalled(); }); 
+1
source

Testing a private method call in the constructor

Isolated unit tests are considered best practice when testing a service using the Angular Testing Guide , meaning there is no need for Angular testing utilities.

We cannot check if the method is called from the constructor by spying on the instance of the object, since this method was already called when we have a reference to the instance.

Instead, we need to look into the prototype of the service ( thanks, Dave Newton! ). When creating methods in the JavaScript class we actually create methods on <ClassName>.prototype .

Given this factory for NgZone spies, based on the MockNgZone of Angular testing internal components :

 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 make fun of the NgZone dependency in order to isolate the service in our tests and even describe outgoing commands that run outside the zone.

 // Straight 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('loads the app once when initialized', () => { expect(GdlService.prototype[methodUnderTest]).toHaveBeenCalledWith(service.appName); expect(GdlService.prototype[methodUnderTest]).toHaveBeenCalledTimes(1); }); it('runs logic outside the zone when initialized.', () => { expect(ngZone.runOutsideAngular).toHaveBeenCalledTimes(1); }); }); }); 

Usually we would not want to test private methods, but instead we observe the public side effects that it does.

However, we can use Jasmine Spies to achieve what we want.

See full example at StackBlitz

Angular Service Lifecycle

See examples demonstrating the Angular service life cycle on StackBlitz . Read the comments in the hello.*.ts files and open the JavaScript console to view the output messages.

Create Angular StackBlitz with Jasmine Tests

Scroll through my StackBlitz to check Angular on Jasmine, as in this answer

+1
source

Source: https://habr.com/ru/post/1274380/


All Articles