javascript - 使用 Angular2 TestBed 模拟具有非具体类接口(interface)参数的服务

标签 javascript angular testbed

我有一个组件,我正在尝试使用 TestBed 进行设置和测试。

该组件包含一个类,该类在构造函数中有一个参数,该参数是一个接口(interface),而不是具体类。我选择使用的任何类(真实类或用于单元测试的 mok 类)都满足此接口(interface)。但是当我在 TestBed 中构建使用此服务的组件时,我不知道如何为 TestBed 配置定义该参数。

这是组件的 TestBed 配置:

describe('PanelContentAreaComponent', () => {
  let component: PanelContentAreaComponent;
  let fixture: ComponentFixture<PanelContentAreaComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ PanelContentAreaComponent
          ],
      providers:[
        MenuCommandService, ProcedureDataService, IOpenService],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    })
    .compileComponents();
  }));

在 TestBed 中构建时遇到问题的服务是 ProcedureDataService 。它的定义如下:

@Injectable()
export class ProcedureDataService {

serverOpenFile: OpenFile;

constructor(private _openService: IOpenService) {
    this.serverOpenFile = emptyFileStatus;
}

ProcedureDataService 的构造函数中的一个参数是 IOnService 其定义是:

export interface IOpenService {
    openFile(fileType: string, dataType: string, filePath: string) ;
}

如您所见,这是一个接口(interface),而不是具体的类。

在我的服务单元测试中,我们通过如下实现来模拟 IOpenService:

export class mockOpenService implements IOpenService{

    constructor(){}

    openFile(fileType: string, dataType: string, filePath: string) {
        let fileContent: OpenFile;
... 
...
[fake the data with mok junk]
...
        fileContent = {
            'filePath': filePath,
            'fileName': name,
            'openSuccess': isSuccess,
            'error': errorMsg,
            'fileData': jsonData
        };

        return Observable.of(fileContent);

    }

}

这在 ProcedureDataService 服务单元测试中效果很好。当然,在实际代码中,我们使用完整实现的文件打开服务来实现 IOpenService,以便正确获取数据。

但是在尝试在组件内部使用此服务时出现以下错误:

PanelContentAreaComponent should create FAILED
        Failed: IOpenService is not defined
        ReferenceError: IOpenService is not defined

这是有道理的,所以我想弄清楚如何告诉 TestBed 我有一个我希望使用的 IOpenService 的具体类实现。我试过了,但失败了:

describe('PanelContentAreaComponent', () => {
  let component: PanelContentAreaComponent;
  let fixture: ComponentFixture<PanelContentAreaComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ PanelContentAreaComponent
          ],
      providers:[
        {provide: IOpenService, useClass: mockOpenService},
        MenuCommandService, ProcedureDataService, IOpenService],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    })
    .compileComponents();
  }));

编译器告诉我:

(31,19): error TS2693: 'IOpenService' only refers to a type, but is being used as a value here.

我仍然得到:

PanelContentAreaComponent should create FAILED
        Failed: IOpenService is not defined
        ReferenceError: IOpenService is not defined

那么我如何指示 TestBed 我有一个特定的类(mockOpenService)实现了一个接口(interface)参数(IOpenService)需要是否提供服务 (ProcedureDataService) 来测试此组件 (PanelContentAreaComponent)?

最佳答案

接口(interface)不能用作 token 。这在 Angular 文档 DI 章节中进行了解释 Dependency injection tokens

TypeScript interfaces aren't valid tokens

export interface AppConfig {
  apiEndpoint: string;
  title: string;
}

export const HERO_DI_CONFIG: AppConfig = {
  apiEndpoint: 'api.heroes.com',
  title: 'Dependency Injection'
};

The HERO_DI_CONFIG constant has an interface, AppConfig. Unfortunately, we cannot use a TypeScript interface as a token:

// FAIL! Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]

// FAIL! Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }

That seems strange if we're used to dependency injection in strongly typed languages, where an interface is the preferred dependency lookup key.

It's not Angular's fault. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces. The TypeScript interface disappears from the generated JavaScript. There is no interface type information left for Angular to find at runtime.

文档继续说明您应该创建一个 OpaqueToken

import { OpaqueToken } from '@angular/core';

export let APP_CONFIG = new OpaqueToken('app.config');

providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }]

constructor(@Inject(APP_CONFIG) config: AppConfig) {
   this.title = config.title;
}

这个例子没问题,但在我们的服务案例中,这不是最优雅的解决方案。就个人而言,我认为更优雅的解决方案是根本不为服务使用接口(interface)。而是使用抽象类。抽象类被转换为实际代码,就像普通类一样。所以你可以将它用作 token

export abstract class IOpenService {
    abstract openFile(fileType: string, dataType: string, filePath: string): any ;
}

class OpenService extends IOpenService {
  openFile(fileType: string, dataType: string, filePath: string): any  {

  }
}

现在你可以做

{ provide: IOpenService, useClass: OpenService }

关于javascript - 使用 Angular2 TestBed 模拟具有非具体类接口(interface)参数的服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42492017/

相关文章:

javascript - 搜索时禁用输入字段并将焦点设置为完成

angular - 检查用户是否仍在 angularfire2 中通过身份验证

angular - ng2-translate:无法读取 TranslatePipe.transform 处未定义的属性 'subscribe'

javascript - 尽管导入了 NgbModule.forRoot(),但使用 ng bootstrap 元素为 Angular 测试平台设置依赖项时出现问题

javascript - Angular 4专注于向下箭头上的元素并滚动

testing - 无法在 Angular 2.0.0 测试中加载 .html

javascript - 比较 html 标签内变量的正则表达式

javascript - 从数组元素设置 img 标签的 src 属性

javascript - 跨房间通讯谜题

javascript - 如何设置通过查看 MVC URL 选择的下拉选项