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

How to mock Injector instance in Angular / Jasmine tests?

我需要测试我的服务,它使用 Injector 来注入服务而不是 constructor()

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

app.module.ts

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

export let InjectorInstance: Injector;

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

简单-data.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));
  }
}

简单-data.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);
});

现在我收到 TypeError: Object prototype may only be an Object or null: undefined 错误消息。这是因为这一行:

this.http = InjectorInstance.get<HttpClient>(HttpClient);

这里的InjectorInstance就是undefined

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

  constructor(
    private model: T,
    private injectorInstance: Injector,
  ) { }

部分问题是您使用 export let 来声明 InjectorInstance。这种类型的声明使得从任何其他文件(如:测试文件)修改字段是非法的。改变它的一种方法是使 InjectorInstance 成为 AppModule class 的静态字段,如:

export class AppModule {
  static InjectorInstance: Injector;

  constructor(private injector: Injector) {
    AppModule.InjectorInstace = injector;
  }
}

那你就可以在该字段中使用TestBed,因为Injector的接口其实很简单,只包含get方法。如:

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

beforeEach(() => {
  AppModule.InjectorInstance = TestBed;
});

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

new AppModule(TestBed)

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

请记住,所有这些都会使您的测试相互影响,因为一旦您更改该字段,它就会从其他所有测试的角度进行更改,即使在其他文件中也是如此。

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