2017-02-27 98 views
2

我有一個組件,我試圖使用TestBed進行設置和測試。使用Angular2 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(); 
    })); 

其具有在測試牀正在建造麻煩的服務是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 

這是有道理的,那麼,我想弄清楚如何告訴測試牀,我有一個具體的類我希望使用這個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,我有提供服務所需的接口參數(IOpenService)的特定類(mockOpenService)實現(ProcedureDataService)被提供以測試該comp onent(PanelContentAreaComponent)?

+0

看起來像你提供「IOpenService」兩次? – vidalsasoon

+0

@vidalsasoon right ...「describe」的最後一個代碼片段列出了{{提供:IOpenService,useClass:mockOpenService}和'IOpenService'。我猜我認爲可能需要這樣做。但是原來的只是'提供者:MenuCommandService,ProcedureDataService,IOpenService]''但是他們兩個都失敗了... –

回答

5

接口不能用作標記。這是在角文檔解釋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; 
} 

這個例子沒關係,但在我們的服務中,這不是最優雅的解決方案。就我個人而言,我認爲更優雅的解決方案是根本不使用接口來提供服務。而是使用抽象類。抽象類被轉換爲真正的代碼,就像普通的類一樣。所以,你可以使用它作爲一個令牌

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 } 
+0

你的答案在我遇到的一個單獨問題上是現貨 - 不一定是關於嘲笑或單元測試。 Interfaces不能用作提供者令牌令人傷心。我真的希望能夠在運行時注入具體的類來代替接口的使用。我嘗試並放棄了OpaqueToken,因爲它仍然迫使我在構造函數簽名中指定注入問題。我用完全抽象的成員將抽象類替換爲我的接口。這使我可以在app.module.ts中使用提供程序令牌在運行時注入我的具體類。 Upvoted和Go GSW! –