Angular 单元测试 - 使用 TestBed 模拟注入服务的异步调用
Angular Unit Testing - Mock async calls of injected services with TestBed
我需要为以下 DataService
,
编写单元测试
@Injectable()
export class DataService {
constructor(private config: ConfigService, private http: HttpClient) {
}
.....
someMethod(){
let apiUrl = this.config.get('api').url; // LINE 1
}
}
ConfigService
被注入到 DataService
,它有一个 load
函数,从 json 文件获取配置。此 load
函数将在应用程序初始化时调用。
export function configServiceFactory(config: ConfigService) {
return () => config.load();
}
...
providers: [
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: configServiceFactory,
deps: [ConfigService],
multi: true
}
]
这是 data-service.spect.ts
文件的一部分,
...
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [DataService, ConfigService]
});
mock = TestBed.get(HttpTestingController);
service = TestBed.get(DataService);
});
....
所以当我 运行 测试时,在 LINE 1
中我得到 this.config.get('api')
未定义。我可以理解这是因为 ConfigService
没有从 JSON 加载数据。那么现在我怎样才能使注入的服务在单元测试期间也进行异步调用?
编写单元测试时,您可能希望模拟您拥有的每个依赖项。您已经通过导入 HttpClientTestingModule
为 HttpClient
执行此操作,因此您需要为 ConfigService
.
执行相同的操作
有两种方法可以做到这一点。
1- 虚假服务(存根)
export class ConfigServiceStub {
get(input: string) {
// return static value instead of calling an API
}
}
...
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [DataService,
{provide: ConfigService, useClass: ConfigServiceStub}
]
});
mock = TestBed.get(HttpTestingController);
service = TestBed.get(DataService);
});
通过这种方式,您的 DataService
将不会调用真正的 ConfigService
,而是调用 ConfigServiceStub
的 get
方法。使用 Stub
时,您无需担心 ConfigService
具有的其他依赖项。您只需实现要覆盖的方法。
2-间谍
您可以在不想调用的方法上创建间谍。
it('run some test', inject([DataService], (service: DataService) => {
spyOn(service.config, 'get').and.returnValue('someString');
// run your tests here
});
即使在上面的示例中不会调用 ConfigService.get
方法,Angular 仍然需要创建一个 ConfigService
的实例,这在某些情况下可能很难做到示例,否则可能会导致为简单测试创建太多其他服务。
我会选择选项 1
有关间谍的更多信息,请查看 here
我需要为以下 DataService
,
@Injectable()
export class DataService {
constructor(private config: ConfigService, private http: HttpClient) {
}
.....
someMethod(){
let apiUrl = this.config.get('api').url; // LINE 1
}
}
ConfigService
被注入到 DataService
,它有一个 load
函数,从 json 文件获取配置。此 load
函数将在应用程序初始化时调用。
export function configServiceFactory(config: ConfigService) {
return () => config.load();
}
...
providers: [
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: configServiceFactory,
deps: [ConfigService],
multi: true
}
]
这是 data-service.spect.ts
文件的一部分,
...
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [DataService, ConfigService]
});
mock = TestBed.get(HttpTestingController);
service = TestBed.get(DataService);
});
....
所以当我 运行 测试时,在 LINE 1
中我得到 this.config.get('api')
未定义。我可以理解这是因为 ConfigService
没有从 JSON 加载数据。那么现在我怎样才能使注入的服务在单元测试期间也进行异步调用?
编写单元测试时,您可能希望模拟您拥有的每个依赖项。您已经通过导入 HttpClientTestingModule
为 HttpClient
执行此操作,因此您需要为 ConfigService
.
有两种方法可以做到这一点。
1- 虚假服务(存根)
export class ConfigServiceStub {
get(input: string) {
// return static value instead of calling an API
}
}
...
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [DataService,
{provide: ConfigService, useClass: ConfigServiceStub}
]
});
mock = TestBed.get(HttpTestingController);
service = TestBed.get(DataService);
});
通过这种方式,您的 DataService
将不会调用真正的 ConfigService
,而是调用 ConfigServiceStub
的 get
方法。使用 Stub
时,您无需担心 ConfigService
具有的其他依赖项。您只需实现要覆盖的方法。
2-间谍
您可以在不想调用的方法上创建间谍。
it('run some test', inject([DataService], (service: DataService) => {
spyOn(service.config, 'get').and.returnValue('someString');
// run your tests here
});
即使在上面的示例中不会调用 ConfigService.get
方法,Angular 仍然需要创建一个 ConfigService
的实例,这在某些情况下可能很难做到示例,否则可能会导致为简单测试创建太多其他服务。
我会选择选项 1
有关间谍的更多信息,请查看 here