1

サービスコールと観測可能な呼び出しを使用してデータリストを取得するAngular 2コンポーネントをテストしようとしています。メインのアプリケーションモジュールをこの仕様ファイルにインポートしました。Observablesを使用するAngular 2コンポーネントのジャスミンテストを書く

import { ComponentFixture, TestBed, fakeAsync, async } from '@angular/core/testing'; 
import { By } from '@angular/platform-browser'; 
import { DebugElement } from '@angular/core'; 
import { MaterialModule } from '@angular/material'; 
import { FormsModule } from '@angular/forms'; 
import { AppModule } from '../../../src/app/app.module'; 
import { Observable } from 'rxjs/Observable'; 
import { Store } from '@ngrx/store'; 
import { } from 'jasmine'; 

import { FirmService } from '../../../src/app/containers/dashboard/services/firm.service'; 
import { FirmListComponent } from '../../../src/app/containers/dashboard/firm-list/firm-list.component'; 
import { mockFirm1, mockFirm2, mockFirms } from './firm-list.mocks'; 
import { Firm } from '../../../src/app/containers/dashboard/models/firm.model'; 
import { FirmState } from '../../../src/app/containers/dashboard/services/firm.state'; 

describe('Firm List Component',() => { 
    let fixture: ComponentFixture<FirmListComponent>; 
    let component: FirmListComponent; 
    let element: HTMLElement; 
    let debugEl: DebugElement; 
    let firmService: FirmService; 
    let mockHttp; 
    let stateObservable: Observable<FirmState>; 
    let store: Store<FirmState>; 
    let getFirmsSpy; 
    let getObservableSpy; 

    // utilizes zone.js in order to mkae function execute syncrhonously although it is asynchrounous 
    beforeEach(async(() => { 
     TestBed.configureTestingModule({ 
      imports: [MaterialModule, FormsModule, AppModule], 
      declarations: [FirmListComponent], 
      providers: [FirmService] 
     }) 
      .compileComponents() // compiles the directives template or any external css calls 
      .then(() => { 
       fixture = TestBed.createComponent(FirmListComponent); // allows us to get change detection, injector 
       component = fixture.componentInstance; 
       debugEl = fixture.debugElement; 
       element = fixture.nativeElement; 
       firmService = fixture.debugElement.injector.get(FirmService); 

       getObservableSpy = spyOn(firmService, 'stateObservable') 
        .and.returnValue(new FirmState()); 

       getFirmsSpy = spyOn(firmService, 'getFirms') 
        .and.returnValue(Observable.of(mockFirms)); 
      }); 
    })); 

    it('should be defined',() => { 
     expect(component).toBeDefined(); 
    }); 

    describe('initial display',() => { 
     it('should not show firms before OnInit',() => { 
      debugEl = fixture.debugElement.query(By.css('.animate-repeat')); 
      expect(debugEl).toBeNull(); 
      expect(getObservableSpy.calls.any()).toBe(false, 'ngOnInit not yet called'); 
      expect(getFirmsSpy.calls.any()).toBe(false, 'getFirms not yet called'); 
     }); 

     it('should still not show firms after component initialized',() => { 
      fixture.detectChanges(); 
      debugEl = fixture.debugElement.query(By.css('.animate-repeat')); 
      expect(debugEl).toBeNull(); 
      expect(getFirmsSpy.calls.any()).toBe(true, 'getFirms called'); 
     }); 

     it('should show firms after getFirms observable', async(() => { 
      fixture.detectChanges(); 

      fixture.whenStable().then(() => { 
       fixture.detectChanges(); 

       // **I get the correct value here, this is the table headers for the table data below that is showing 0** 
       var rowHeaderLength = element.querySelectorAll('th').length; 
       expect(rowHeaderLength).toBe(8); 

       // **I get 0 for rowDataLength here, test fails** 
       var rowDataLength = element.querySelectorAll('.animate-repeat').length; 
       console.log(rowDataLength); 
      }); 
     })); 

     it('should show the input for searching',() => { 
      expect(element.querySelector('input')).toBeDefined(); 
     }); 
    }); 
}); 

最初のパス上記の試験が、二番目は、私が現在「ヌルの 『nativeElement』プロパティを読み取ることができません」というエラーを取得していません。

マイspecファイルは次のようになります。

私のコンポーネントのコードは次のようになります。私は何かを欠けている場合

import { NgModule, Component, Input, OnInit, OnChanges } from '@angular/core'; 
import { MaterialModule } from '@angular/material'; 
import { FlexLayoutModule } from '@angular/flex-layout'; 
import { CommonModule } from '@angular/common'; 
import { FormsModule } from '@angular/forms'; 
import { Firm } from '../models/firm.model'; 
import { FirmService } from '../services/firm.service'; 

@Component({ 
    selector: 'firm-list', 
    templateUrl: './firm-list.html', 
    styles: [] 
}) 

export class FirmListComponent implements OnInit { 
    public selectAll: boolean; 
    public firms: Array<Firm>; 
    public filteredFirms: any; 
    public loading: boolean; 
    public searchText: string; 
    private componetDestroyed = false; 

    // @Input() public search: string; 

    constructor(public firmService: FirmService) { } 

    public ngOnInit() { 
     this.firmService.stateObservable.subscribe((state) => { 
     this.firms = state.firms; 
     this.filteredFirms = this.firms; 
     }); 

     this.getFirms(); 
    } 

    public getFirms(value?: string) { 
     this.loading = true; 
     this.firmService.getFirms(value).subscribe((response: any) => { 
     this.loading = false; 
     }); 
    } 
} 

@NgModule({ 
    declarations: [FirmListComponent], 
    exports: [FirmListComponent], 
    providers: [FirmService], 
    imports: [ 
     MaterialModule, 
     FlexLayoutModule, 
     CommonModule, 
     FormsModule 
    ] 
}) 

export class FirmListModule { } 

は、私は私が観察を考慮して、私のspecファイルにいくつかのコードを欠けているのかはわかりませんか?どんな助けもありがとうございます。

事務所サービス

import { Observable } from 'rxjs/Rx'; 
import { Injectable } from '@angular/core'; 
import { AuthHttp } from 'angular2-jwt'; 
import { Response } from '@angular/http'; 
import { Store } from '@ngrx/store'; 
import { firmActions } from './firm.reducer'; 
import { FirmState } from './firm.state'; 

@Injectable() 
export class FirmService { 
    public stateObservable: Observable<FirmState>; 

    constructor(private $http: AuthHttp, private store: Store<FirmState>) { 
    // whatever reducer is selected from the store (in line below) is what the "this.store" refers to in our functions below. 
    // it calls that specific reducer function 
    // how do I define this line in my unit tests? 
     this.stateObservable = this.store.select('firmReducer'); 
    } 

public getFirms(value?: string) { 
    return this.$http.get('/api/firm').map((response: Response) => { 
     this.store.dispatch({ 
      type: firmActions.GET_FIRMS, 
      payload: response.json() 
     }); 
     return; 
    }); 
} 

public firmSelected(firms) { 
    // takes in an action, all below are actions - type and payload 
    // dispatches to the reducer 
    this.store.dispatch({ 
     type: firmActions.UPDATE_FIRMS, 
     payload: firms 
    }); 
} 

public firmDeleted(firms) { 
    this.store.dispatch({ 
     type: firmActions.DELETE_FIRMS, 
     payload: firms 
    }); 
    } 
} 

私の事務所のコンポーネントHTMLテンプレート:

<md-card class="padding-none margin"> 
    <div class="toolbar" fxLayout="row" fxLayoutAlign="start center"> 
    <div fxFlex class="padding-lr"> 
     <div *ngIf="anySelected()"> 
     <button color="warn" md-raised-button (click)="deleteSelected()">Delete</button> 
     </div> 
     <div *ngIf="!anySelected()"> 
     <md-input-container floatPlaceholder="never"> 
      <input mdInput [(ngModel)]="searchText" (ngModelChange)="onChange($event)" type="text" placeholder="Search" /> 
     </md-input-container> 
     </div> 
    </div> 
    <div class="label-list" fxFlex fxLayoutAlign="end center"> 
     <label class="label bg-purple600"></label> 
     <span>EDF Model</span> 
     <label class="label bg-green600"></label> 
     <span>EDF QO</span> 
     <label class="label bg-pink800"></label> 
     <span>LGD Model</span> 
     <label class="label bg-orange300"></label> 
     <span>LGD QO</span> 
    </div> 
    </div> 
    <md-card-content> 
    <div class="loading-container" fxLayoutAlign="center center" *ngIf="loading"> 
     <md-spinner></md-spinner> 
    </div> 
    <div *ngIf="!loading"> 
     <table class="table"> 
     <thead> 
      <tr> 
      <th class="checkbox-col"> 
       <md-checkbox [(ngModel)]="selectAll" (click)="selectAllChanged()" aria-label="Select All"></md-checkbox> 
      </th> 
      <th> 
       Firm Name 
      </th> 
      <th> 
       Country 
      </th> 
      <th> 
       Industry 
      </th> 
      <th> 
       EDF 
      </th> 
      <th> 
       LGD 
      </th> 
      <th> 
       Modified 
      </th> 
      <th> 
       Modified By 
      </th> 
      </tr> 
     </thead> 
     <tbody> 
      <tr *ngFor="let firm of filteredFirms; let i = index" class="animate-repeat" [ngClass]="{'active': firm.selected}"> 
      <td class="checkbox-col"> 
       <md-checkbox [(ngModel)]="firm.selected" aria-label="firm.name" (change)="selectFirm(i)"></md-checkbox> 
      </td> 
      <td>{{firm.name}}</td> 
      <td>{{firm.country}}</td> 
      <td>{{firm.industry}}</td> 
      <td> 
       <span class="label bg-purple600">US 4.0</span> 
       <span class="label bg-green600">US 4.0</span> 
      </td> 
      <td> 
       <span class="label bg-pink800">US 4.0</span> 
       <span class="label bg-orange300">US 4.0</span> 
      </td> 
      <td>{{firm.modifiedOn}}</td> 
      <td>{{firm.modifiedBy}}</td> 
      </tr> 
     </tbody> 
     </table> 
    </div> 
    </md-card-content> 
</md-card> 
+0

[docsに表示されている](https://angular.io/docs/ts/latest/guide/testing.html#!#component-with-external-template)の設定を採用しようとしましたか? – jonrsharpe

+0

私はbeforeEachとテスト自体の間でコードを前後に移動しようとしましたが、そこにどのようなセットアップをお勧めしますか?私は設定前にDOMをテストしているので、null nativeElementを取得していますか?私はサービスを模擬するだけで、プロバイダーに本物のものを追加する必要はありませんか? – bschmitty

+0

さて、それを読んでください。* 2つの 'beforeEach'セクションを推奨しています。一つは' async'セクションです。あなたの現在のセットアップで 'createComponent'が期待通りに達する前に起こっていないようです。 – jonrsharpe

答えて

1

うーん、私はあなたの問題になる可能性がここにカップルの事を参照してください。あなたのエラーはここに来ています:

de = fixture.debugElement.query(By.css('table')); 

nullのnullのnativeElementを取得しようとしました。あなたがこの問題を解決したと仮定して、それが存在しない理由はありません - あなたは自分自身を健全にチェックし、存在するはずの他の要素をつかむことができますが、ここの問題は何かへの参照それが存在する前に。この場合、nativeElementへの参照を取得しようとした後に変更を検出します。あなたのテーブルに私が思うようにデータが挿入されている場合は、まず変更を検出してからDOMに伝播するものへの参照を取得する必要があります。あなたのngOnInitがまだ発生していないことを確認してください。TestBedがコンポーネントフィクスチャを作成するときにトリガされません。最初のdetectChanges()で発生します。

これを試してみてください:

it('should have table headers',() => { 
     fixture.detectChanges(); 
     de = fixture.debugElement.query(By.css('table')); 
     el = de.nativeElement;   
     expect(el.textContent).toEqual('Firm Name'); 
    }); 

それはさらに進んで - カバーの下の角度のアニメーションを使用したテーブルか何かで多くの時間をBrowserAnimationsModuleまたはNoopAnimationsModuleのいずれかをインポートする必要があります。これは単体テストなので、NoopAnimationsModuleを読み込んで参照を取得し、好きなようにテストを実行します。

あなたがngOnInitに乗っている間違いを指摘した後、私はあなたの問題が何かを見ています。

このユニットテストは、そのサービスをテストするためのものではありません。その考えの中で、いくつかの選択肢があります。スパイを使ってサービスへの呼び出しを傍受しますが、それはプロパティなので、spyOnPropertyを使用する必要があります。とにかく、あなたが提供していたスタブを使用することもできます。元の投稿を確認して、私はそれがあなたがやろうとしていることだと思います。私はあなたがそれをこのように変更した場合、これはうまくいくかもしれないと思う:それはngOninitにアクセスされていますよう

beforeEach(async(() => { 
    TestBed.configureTestingModule({ 
     imports: [MaterialModule, FormsModule, AppModule], 
     declarations: [FirmListComponent], 
     providers: [{provide: FirmService, useClass: FirmStub}] 
    }) 
     .compileComponents() 
     .then(() => { 
      fixture = TestBed.createComponent(FirmListComponent); 
      component = fixture.componentInstance; 
      firmStub = fixture.debugElement.injector.get(FirmService); 
     }); 
})); 

そのノートで、あなたも、あなたのFirmStubにstateObservableプロパティを提供する必要があります。あなたはそれを比較的まっすぐ前方にスタブすることができます。htmlファイルなし

class FirmStub { 
    public stateObservable: Observable<FirmState> = new Observable<FirmState>(); 
    public getFirms(value?: string): Observable<any> { 
    return Observable.of(mockFirms); 
    } 
} 

、私はあなたが実際にプロパティがテンプレートをテストするための特定の方法で移入することを必要かどうかわからないんだけど、いない場合は、そのスタブは動作するはずです。あなたが特定の方法でそれを必要とするならば、FirmStubにもっと頑強な財産を提供させるだけです。

はまた、単にテストにこれを追加することによって、すべて一緒にngOnInitを傍受することができます:

spyOn(component, 'ngOnInit');// this will basically stop anything from ngOnInit from actually running. 

ホープ、このことができます!

+0

よろしいですか、私がfixture.detectChanges()をel.nativElementの上に移動したときの問題は、私が実際に私の会社をページにロードしようとしているngOnInitの中の私のコンポーネントコードから来ている "nullのnullを購読する"プロパティを読むことができません。本質的にfixture.detectChangesはngOnInitを呼び出しています。 'stateObservable'の値を取得し、テストを終了します。 fixture.detectChanges()を取り出して、スパイが呼び出されたかどうかをテストするか、基本テストを行っても問題ないです。任意のアイデアはなぜ私のコンポーネントで私はnullになる 'stateObservable'? – bschmitty

+0

私はこの時点で私がどこにいるのか、いくつかのコードを追加しました - FirmStubを追加し、overrideComponentを追加してコンポーネントから抜け出しましたが、動作していないようです... ngOnInitの内部で未定義になるのですか? – bschmitty

+1

申し訳ありませんが、私は明確にされている必要があります。私はあなたの変化の検出を上げるように言おうとしています。これにより、DOMの変更が伝播され、それらのノードへの参照を取得できます。私は私の答えを更新します。 – Angelo

関連する問題