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 加载数据。那么现在我怎样才能使注入的服务在单元测试期间也进行异步调用?

编写单元测试时,您可能希望模拟您拥有的每个依赖项。您已经通过导入 HttpClientTestingModuleHttpClient 执行此操作,因此您需要为 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,而是调用 ConfigServiceStubget 方法。使用 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