【Angular】Componentテスト(クラス・DOM)
概要
Angular のComponentテストではクラスをテストする場合とDOMをテストする場合で、TestBed.configureTestingModule
の設定方法が変わってきます。
設定方法や使い分けについて記載します。
実行環境
- Node.js - 10.9.x
使用ライブラリ
- Angular - 7.2.x
クラス・DOMテストの使い分け
クラステストについて
- Input, Output, メソッド単位でテストを実装する場合
- カバレッジを意識したテストを実装する場合
- DOMテストより
TestBed.configureTestingModule
の設定が簡略化。imports
箇所が削減できます。
DOMテストについて
使い分け方
- 各プロジェクト・チームごとに異なりますが、カバレッジを消化するためのユニットテストの範囲であればクラスのテストで問題ありません。
- クラスのテストの方がDOM構築を行わないためテストが速く終わります。CIなどで実行する場合、クラスのテストの方が良い場合があります。
- DOMテストの場合、E2Eやクラステストとのテスト範囲をチーム内で決めておく必要があります。
サンプルソース
テスト対象
app.component.ts
import { Component, OnInit } from '@angular/core'; import { AppService } from './app.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'ng-sample'; name = ''; text = ''; constructor(private appService: AppService) {} ngOnInit(): void { this.text = 'aaa'; } onClick(): void { this.appService.getData().subscribe(data => { this.name = data.name; }); } }
app.service.ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; export interface JsonData { id: number; name: string; age: number; } @Injectable({ providedIn: 'root', }) export class AppService { readonly URL = './assets/data.json'; constructor(private http: HttpClient) { } getData(): Observable<JsonData> { return this.http.get<JsonData>(this.URL); } }
クラステスト
import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { of } from 'rxjs'; import { AppComponent } from './app.component'; import { AppService } from './app.service'; describe('AppComponent - Class Test', () => { let component: AppComponent; let appService: AppService; beforeEach(() => { // providers に AppComponent を設定 TestBed.configureTestingModule({ providers: [ AppComponent, { provide: AppService, useValue: {} } ] }); component = TestBed.get(AppComponent); appService = TestBed.get(AppService); }); it('should create', () => { expect(component).toBeDefined(); }); it(`should have as title 'ng-sample'`, () => { expect(component.title).toEqual('ng-sample'); }); it('ngOnInit', () => { // exercise component.ngOnInit(); // verify expect(component.text).toBe('aaa'); }); it('onClick', fakeAsync(() => { // setup appService.getData = jasmine.createSpy().and.returnValue(of({ name: 'hoge' })); // exercise component.onClick(); tick(); // verify expect(component.name).toBe('hoge'); })); });
DOMテスト
import { TestBed, async, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; import { of } from 'rxjs'; import { AppComponent } from './app.component'; import { AppService } from './app.service'; describe('AppComponent - DOM Test', () => { let component: AppComponent; let fixture: ComponentFixture<AppComponent>; let appService: AppService; beforeEach(async(() => { // compileComponents 後、ComponentFixture 経由で AppComponent を取得 TestBed.configureTestingModule({ declarations: [ AppComponent ], imports: [ FormsModule ], providers: [ { provide: AppService, useValue: {} } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(AppComponent); component = fixture.componentInstance; appService = TestBed.get(AppService); }); it('should create', () => { expect(component).toBeDefined(); }); it(`should have as title 'ng-sample'`, () => { expect(component.title).toEqual('ng-sample'); }); it('should render title in a h1 tag', () => { fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain('Welcome to ng-sample!'); }); it('ngOnInit', () => { // exercise component.ngOnInit(); // verify expect(component.text).toBe('aaa'); }); it('onClick', fakeAsync(() => { // setup appService.getData = jasmine.createSpy().and.returnValue(of({ name: 'hoge' })); // exercise component.onClick(); tick(); // verify expect(component.name).toBe('hoge'); })); });
サンプルソース一式
個人的な使い分け
クラステスト
- Componentのロジック周りのテスト
- カバレッジ消化
- CIで利用前提
DOMテスト
- Componentの表示周りのテスト
- データバインディングやPipeで変換された表示内容を確認
終わりに
- クラスのユニットテストを意識することで、Componentのコードがきれいになることが多いのでテストは意識した方が良い。
- クラス・DOMとテスト速度に差が出るため、適用範囲についてチーム内のルール決めが必須。
- テストコードの維持コストは e2e > DOM > クラス になるため、テスト観点が重要になる。