使用Angular2 TestBed模拟具有非具体类接口参数的服务 [英] Using Angular2 TestBed to Mock a service with a non-concrete class interface parameter

查看:94
本文介绍了使用Angular2 TestBed模拟具有非具体类接口参数的服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a component that I am trying to setup and test using TestBed.

此组件包含一个类,该类在构造函数中具有作为接口的参数,而不是具体的类.无论我选择使用哪个类(用于单元测试的真实类或mok类),都可以满足此接口.但是,当我在TestBed中构造使用此服务的组件时,我无法弄清楚如何为TestBed配置定义该参数.

This component contains one class that has a parameter in the constructor that is an interface, not a concrete class. This interface is satisfied by whatever class I choose to use (either the real one, or a mok one for unit testing). But when I am constructing the component that uses this service in TestBed, I cannot figure out how to define that parameter to the TestBed configuration.

这是该组件的TestBed配置:

Here is the TestBed config for the component:

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.定义如下:

The service which is having trouble being constructed in TestBed is ProcedureDataService . It's definition is below:

@Injectable()
export class ProcedureDataService {

serverOpenFile: OpenFile;

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

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

The one parameter in the constructor of the ProcedureDataService is IOpenService whose definition is :

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

如您所见,这是一个接口,而不是具体的类.

As you can see this is an interface, not a concrete class.

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

In my service unit test, we mock the IOpenService by implementing it as follows:

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,以正确获取数据.

This works great in the ProcedureDataService service unit test. And, of course, in the real code, we implement the IOpenService with the full implemented file open service that gets the data properly.

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

But in trying to use this service inside of a component I get the following error:

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

这很有道理,因此我试图找出如何告诉TestBed我想要使用此IOpenService的具体类实现.我试过了,但是失败了:

This makes sense, so then I am trying to figure out how to tell TestBed that I have a concrete class implementation of this IOpenService which I wish to use. I tried this, but it fails:

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我具有提供用于测试此组件(ProcedureDataService)所需的接口参数(IOpenService)的特定类(mockOpenService)实现>)?

So how do I instruct TestBed that I have a specific class (mockOpenService) implementation of an interface parameter (IOpenService) needed for the service (ProcedureDataService) being provided to test this component (PanelContentAreaComponent)?

推荐答案

接口不能用作标记. Angular docs DI章节依赖注入令牌

Interfaces can't be used as token. This is explained in the Angular docs DI chapter Dependency injection tokens

TypeScript接口不是有效的令牌

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

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

HERO_DI_CONFIG常量具有接口AppConfig.不幸的是,我们不能使用TypeScript接口作为令牌:

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.

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

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.

The docs goes on to explain that you should create an 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;
}

在这个示例中可以,但是对于我们的服务而言,这不是最优雅的解决方案.就我个人而言,我认为更优雅的解决方案是根本不使用用于服务的接口.而是使用抽象类.就像普通的类一样,抽象类也可以转换为实际代码.因此您可以将其用作令牌

It's ok for this example, but in our case of the service, this is not the most elegant solution. Personally, I think the more elegant solution is to not use interfaces at all for services. Instead use abstract classes. Abstract classes are transpiled to real code, just as a normal class would be. So you can use it as a 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 }

这篇关于使用Angular2 TestBed模拟具有非具体类接口参数的服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆