はじめに
Angularのコンポーネントテストは、同じようなコードを何度も書いたり、非同期回りがよくわからなかったり、少々つらみがあります。
ここでは、以下の2つのライブラリの書き心地を比較してみたいと思います。
- Testing Library
- Spectator (Akitaの作者が作っているそうです)
題材
題材として、AngularチュートリアルのTour of Heroesを使います。
インストール方法
npmを使うだけです。
Testing Library
npm install @testing-library/angular --save-dev
Spectator
npm install @ngneat/spectator --save-dev
コンポーネントインスタンス(テスト対象)の生成
オリジナル
オリジナルです。不要なものもある気もしますが長いですね。
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
let heroService;
let getHeroesSpy;
beforeEach(
waitForAsync(() => {
heroService = jasmine.createSpyObj('HeroService', ['getHeroes']);
getHeroesSpy = heroService.getHeroes.and.returnValue(of(HEROES));
TestBed.configureTestingModule({
declarations: [DashboardComponent, HeroSearchComponent],
imports: [RouterTestingModule.withRoutes([])],
providers: [{ provide: HeroService, useValue: heroService }],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
Testing Library
render
関数を呼ぶことで、表示結果込みのコンポーネントを生成できます。
let component: RenderResult<DashboardComponent, DashboardComponent>;
let heroService;
let getHeroesSpy;
beforeEach(async () => {
heroService = jasmine.createSpyObj('HeroService', ['getHeroes']);
getHeroesSpy = heroService.getHeroes.and.returnValue(of(HEROES));
component = await render(DashboardComponent, {
providers: [{ provide: HeroService, useValue: heroService }],
});
});
Spectator
最初にファクトリを作ってから、コンポーネントを生成します。
let component: Spectator<DashboardComponent>;
const createComponent = createComponentFactory(DashboardComponent);
let heroService;
let getHeroesSpy;
beforeEach(() => {
heroService = jasmine.createSpyObj('HeroService', ['getHeroes']);
getHeroesSpy = heroService.getHeroes.and.returnValue(of(HEROES));
component = createComponent({
providers: [{ provide: HeroService, useValue: heroService }],
});
});
コンポーネントが作成されたかテスト
このあたりの書き方は変わるところないですね。
it('should be created', () => {
expect(component).toBeTruthy();
});
Testing Library / Spectorでテストしているのはコンポーネントなのか?という疑問はあります。
ヘッドラインに'Top Heroes'と出力されているかテスト
オリジナル
it('should display "Top Heroes" as headline', () => {
expect(fixture.nativeElement.querySelector('h2').textContent).toEqual(
'Top Heroes'
);
});
Testing Library
h2の中のテキストを取得します。取得できなければ例外が発生するので、expectしなくてよいです。
it('should display "Top Heroes" as headline', () => {
component.getByText('Top Heroes', { selector: 'h2' });
});
Spectator
オリジナルより短く書けます。
it('should display "Top Heroes" as headline', () => {
expect(component.query('h2').textContent).toEqual('Top Heroes');
});
HeroServiceが呼ばれているかテスト
jasmineスパイのテストですので、書き方は変わらないですね。
it('should call heroService', waitForAsync(() => {
expect(getHeroesSpy.calls.any()).toBe(true);
})
);
リンクが4つ出力されているかテスト
a
タグの数を数えています。
オリジナル
it('should display 4 links', waitForAsync(() => {
expect(fixture.nativeElement.querySelectorAll('a').length).toEqual(4);
})
);
Testing Library
queryAllByText
で全マッチして数を数えています。
it('should display 4 links', () => {
expect(component.queryAllByText(/./, { selector: 'a' }).length).toEqual(4);
});
Spectator
オリジナルより短く書けます。
it('should display 4 links', () => {
expect(component.queryAll('a').length).toEqual(4);
});
まとめと感想
スター数とダウンロード数は2021年4月12日現在の値です。
Testing Library | Spectator | |
---|---|---|
コンポーネント(テスト対象)の生成 |
TestBed を用意せず、すっぱり書ける |
TestBed を用意せず、すっぱり書ける |
エレメントの取得 | get...By、query...Byは、ちょっと癖があるかもしれない | ほぼほぼオリジナルのテストと同じ |
GitHubスター数 | 3811 | 1,413 |
週間ダウンロード数 | 17,820 | 41,504 |
Angular以外も使っていたり、これから使う可能性がある場合は、ReactやVue等でも使えるTesting Libraryがよさそうです。
Angularのテストに慣れていたり、既存のテストを移行する場合は、Spectatorがわかりやすくてよさそうです。
作成したファイルは https://github.com/sengokyu/ex.angular-testing に置きました。
リンク
-
使用している@testing-library/domのスター数は2,530です。 ↩