使用Angular2 TestBed模拟具有非具体类接口参数

时间:2017-02-27 17:25:16

标签: javascript angular testbed

我正在尝试使用TestBed设置和测试组件。

此组件包含一个在构造函数中具有参数的类,该参数是接口,而不是具体类。我选择使用的任何类(无论是真实的,还是用于单元测试的mok)都满足此接口。但是当我构建在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的构造函数中的一个参数是IOpenService,其定义为:

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

正如您所看到的,这是一个界面,而不是具体的类。

在我的服务单元测试中,我们通过如下实现来模拟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)实现了服务(IOpenService)所需的接口参数(ProcedureDataService)提供测试此组件(PanelContentAreaComponent)?

1 个答案:

答案 0 :(得分:6)

接口不能用作令牌。这在Angular docs DI章节Dependency injection tokens

中有解释
  

TypeScript接口无效令牌

<div class="parent"><span class="child">Child elements</span></div>
     

export interface AppConfig { apiEndpoint: string; title: string; } export const HERO_DI_CONFIG: AppConfig = { apiEndpoint: 'api.heroes.com', title: 'Dependency Injection' }; 常量有一个接口HERO_DI_CONFIG。不幸的是,我们不能将TypeScript接口用作令牌:

AppConfig
     

如果我们习惯在强类型语言中使用依赖注入,那么这似乎很奇怪,其中接口是首选的依赖项查找键。

     

这不是Angular的错。接口是TypeScript设计时工件。 JavaScript没有接口。 TypeScript接口从生成的JavaScript中消失。 Angular没有留下接口类型信息以便在运行时找到。

文档继续解释您应该创建// 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){ }

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;
}

现在你可以做到

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  {

  }
}