如何在 Angular / Jasmine 测试中模拟 Injector 实例?

net*_*djw 6 mocking jasmine testbed karma-jasmine angular

我需要测试我的服务,该服务用于Injector注入服务而不是constructor().

我使用这种方式的主要原因是大量的服务扩展了我的共同点SimpleDataService。这里有CompanyService,等等,大家都扩展SimpleDataService ProductServiceTagService所以我不想为super()调用定义超过必要的参数。

应用程序模块.ts

import { Injector, NgModule } from '@angular/core';

export let InjectorInstance: Injector;

@NgModule({
  // ...
})
export class AppModule {
  constructor(private injector: Injector) {
    InjectorInstance = this.injector;
  }
}
Run Code Online (Sandbox Code Playgroud)

简单数据.service.ts

import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { InjectorInstance } from 'src/app/app.module';
import { PaginateInterface } from 'src/app/models/paginate/paginate.interface';
import { environment } from 'src/environments/environment';

export class SimpleDataService<T> {
  public url = environment.apiUrl;
  private http: HttpClient;
  public options: any;
  public type: new () => T;

  constructor(
    private model: T,
  ) {
    this.http = InjectorInstance.get<HttpClient>(HttpClient);
  }

  getAll(): Observable<PaginateInterface> {
    return this.http.get(this.url + this.model.api_endpoint, this.options)
      .pipe(map((result: any) => result));
  }
}
Run Code Online (Sandbox Code Playgroud)

简单数据.service.spec.ts

import { HttpClient } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule,
  platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import { Tag } from 'src/app/models/tag/tag.model';
import { SimpleDataService } from './simple-data.service';

describe('SimpleDataService', () => {
  TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());

  const model = new Tag();
  const simpleDataService = new SimpleDataService(model);
});
Run Code Online (Sandbox Code Playgroud)

现在我收到TypeError: Object prototype may only be an Object or null: undefined错误消息。这是由于这一行而发生的:

this.http = InjectorInstance.get<HttpClient>(HttpClient);
Run Code Online (Sandbox Code Playgroud)

就是InjectorInstance这里undefined

如何将Injector实例模拟到我的InjectorInstance属性中以避免这种方式?:

  constructor(
    private model: T,
    private injectorInstance: Injector,
  ) { }
Run Code Online (Sandbox Code Playgroud)

Apo*_*psa 4

部分问题是您正在使用export let来声明InjectorInstance. 这种类型的声明使得从任何其他文件(例如:测试文件)修改该字段都是非法的。改变这一点的一种方法是将类InjectorInstance的静态字段设置为静态字段AppModule,如下所示:

export class AppModule {
  static InjectorInstance: Injector;

  constructor(private injector: Injector) {
    AppModule.InjectorInstace = injector;
  }
}
Run Code Online (Sandbox Code Playgroud)

那么你可以在该字段中使用TestBed,因为 的接口Injector实际上非常简单,只包含get方法。如:

beforeEach(async(() => {
  TestBed.configureTestingModule({(...)});
}));

beforeEach(() => {
  AppModule.InjectorInstance = TestBed;
});
Run Code Online (Sandbox Code Playgroud)

在当前的实现中,您还应该能够简单地执行以下操作:

new AppModule(TestBed)
Run Code Online (Sandbox Code Playgroud)

我认为这行代码的描述性较差,但它使您的InjectorInstance生产代码更加安全。

请记住,所有这些都会使您的测试相互影响,因为一旦更改该字段,从每个其他测试的角度来看,即使在其他文件中,它也会发生更改。

将依赖注入模式替换为服务定位器模式是否是一个好主意是一个完全不同的讨论。