我正在尝试使用Karma-Jasmine测试我的Angular服务,我需要确保在服务初始化后调用loadApp
函数。测试它的最佳方法是什么?
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
});
}
}
答案 0 :(得分:3)
尝试模拟ngZone的注入(我喜欢这种东西的ts-mockito),然后检查是否已经调用了ngZone.outsideOfAngular。由于打字稿的性质,我不认为你能够直接窥探任何私密的东西。
测试文件中的内容如下:
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();
});
});
如果您希望仅使用spyOn方法,则只需替换提供程序的useValue部分中的对象。
答案 1 :(得分:1)
它可以像任何其他功能一样进行测试。考虑到loadApp
是原型方法,它可以在类原型上存根或监视:
it('', () => {
spyOn(<any>GdlService.prototype, 'loadApp');
const gdl = TestBed.get(GdlService);
expect(gdl['loadApp']).toHaveBeenCalledWith('myAppName');
});
答案 2 :(得分:1)
提高成员的可见性以进行测试是可以的。因此,为了优雅,您可能希望将loadApp公开用于模拟。然而,尝试模拟私有函数会带来一些权衡。 @estus正在回答它:
我稍微调整了一下,使用jasmine.createSpy修改原型以覆盖私有函数。
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();
});
答案 3 :(得分:1)
Isolated unit tests被视为best practice when testing a service by the Angular Testing Guide,即不需要Angular测试实用程序。
我们无法通过监视对象实例来测试从构造函数调用的方法,因为一旦我们引用了该实例,该方法就已被调用。
相反,我们需要监视服务的原型(thanks, Dave Newton!)。在JavaScript Collection {#910 ▼
#items: array:2 [▼
"Master Classes" => Collection {#907 ▶}
"Day By Day" => Collection {#908 ▶}
]
}
上创建方法时,我们实际上是在class
上创建方法。
鉴于这个基于MockNgZone from Angular testing internals的NgZone间谍工厂:
<ClassName>.prototype
我们可以模拟NgZone依赖项以在我们的测试中隔离服务,甚至描述在区域外运行的传出命令。
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;
}
通常我们不想测试私有方法,而是观察它所产生的公共副作用。
但是,我们可以使用Jasmine Spies来实现我们想要的目标。
See full example on StackBlitz
See examples that demonstrate the Angular Service Lifecycle on StackBlitz。请阅读// 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);
});
});
});
文件中的注释,然后打开JavaScript控制台以查看输出的消息。
Fork my StackBlitz用Jasmine测试Angular就像在这个答案中一样