最近在写前端单元测试中遇到了一个多层依赖的问题。
由于项目中使用了Ionic中的UI框架,在一些服务(service)文件中需要使用到一些弹框、菊花加载条、对话框等组件,所以会在service文件中依赖注入AlertController Loading等组件。在对这些service进行测试的时候,需要注入这些依赖。如
TestBed.configureTestingModule({
imports:[...],
providers:[AlertController,Loading]
})
这样看起来似乎很完美,service中需要的依赖都通过测试机床注入了。
然鹅,执行测试的时候报错了,No Provider for App!这是什么鬼 App这个依赖是哪来的?原来AlertController中有依赖,见源码
export declare class AlertController {
private _app;
config: Config;
constructor(_app: App, config: Config);
/**
* Display an alert with a title, inputs, and buttons
* @param {AlertOptions} opts Alert. See the table below
*/
create(opts?: AlertOptions): Alert;
}
这样下去,何时才能将所有的依赖加入进来?所以这种问题改如何解决?答案是使用Spy。
其实这些辅助组件并不是业务逻辑的核心,我们完全可以Spy掉它,然后只验证是否调用过即可。
以AlertController为例,这个组件中有create present dismiss等方法,我们需要mock一个出来,然后在依赖注入时,使用mock出来的对象。
beforeEach(
() => {
/**service中有AlertController注入,AlertController中又有其他注入,需要对AlertController进行mock
* 解决循环依赖注入问题,并且我们的测试关注的是业务逻辑而非这些组件,我们只需判断他们是否调用过
*/
alertControllerMock = {
create: ( args: any ) => { return this },
present: jasmine.createSpy( 'present' ),
dismiss: jasmine.createSpy( 'dismiss' )
}
spyOn( alertControllerMock, 'create' ).and.returnValue( alertControllerMock );
TestBed.configureTestingModule( {
imports: [ ... ],
providers: [
{ provide: AlertController, useValue: alertControllerMock }
]
} );
}
);
首先mock一个alertControllerMock,里面含有三个方法,其中create方法返回了自身,另外present和dismiss均是假函数。假函数内部没有可以执行的代码。
spyOn(obj,funcName).and.returnValue(alertControllerMock),create方法返回了alertControllerMock。
完成之后,我们在测试过程中,只需要验证其被调用过即可完成测试过程。
expect( alertControllerMock.present ).toHaveBeenCalled();
总结,这里面涉及到测试中的一个规则,就是一些逻辑等并且是主要过程,我们可以只测试它们是否执行过,至于执行返回了什么我们不关心,只需要知道这行代码被执行过,这段逻辑被覆盖过即可。对于这种情况我们可以采用spy来协助单元测试。
参考链接:
https://stackoverflow.com/questions/39977505/angular-2-mock-ionic2-no-provider-for-app