Testing Libraryは、コンポーネントの内部実装ではなく、ユーザーから見える振る舞いを中心にテストするための考え方とツール群です。特に getByRole のように、画面上の役割やアクセシビリティ情報を手がかりに要素を探すと、DOM構造やクラス名の変更に引っ張られにくくなります。Angular Testing Libraryは、この考え方をAngularで実践しやすくするための道具で、TestBedに寄りすぎた“実装詳細テスト”から離れやすいのが特徴です。userEvent を使えば、クリックや入力も人の操作に近い形で再現しやすくなり、壊れにくく読めるテストへ寄せやすくなります。

関連: AngularをJestに移行:Karma/Jasmineから最短手順(ESM/CIの詰まり対策)
1. Testing Libraryの思想(結論)
1-1. 実装詳細ではなく“振る舞い”をテストする
Testing Libraryの一番大事な考え方は、コンポーネントの内部実装ではなく、ユーザーから見える振る舞いをテストすることです。つまり、「このメソッドが呼ばれたか」よりも、「画面に何が表示され、何を操作できるか」を優先して確認します。
実装詳細に寄ったテストは、内部メソッド名の変更、DOMの細かい構造変更、クラス分割などで壊れやすくなります。一方、ユーザー視点のテストは、見た目や役割が同じなら内部実装が変わっても通りやすいです。これが「壊れにくいテスト」と言われる理由です。Angularでは特に、TestBedでコンポーネント内部へ直接触れやすいぶん、この考え方を意識しないと実装依存のテストが増えがちです。
たとえば「保存ボタンを押したら成功メッセージが出る」を確認したいなら、コンポーネントの private な状態を見るより、ボタンを押してメッセージが見えるかを見るほうが自然です。チェック観点としては、「このテストはユーザーが体験する結果を見ているか」を毎回問い直すと、実装詳細へ寄りすぎるのを防ぎやすくなります。
1-2. TestBedとの役割分担
Angular Testing Libraryは、TestBedを置き換えるものではなく、TestBedの上で“ユーザー視点の書き方”をしやすくする道具です。つまり、Angularの実行環境はTestBedが作り、その上でTesting Libraryが読みやすいテストの書き方を提供します。
TestBedは、DI、テンプレート、変更検知、Angular固有の実行環境を用意する役目です。一方で、Testing Libraryは render、screen、クエリ群を通じて、「どう描画して」「どう探して」「どう操作するか」をシンプルにします。これにより、fixtureやnativeElementを細かく触るコードを減らしやすくなります。
実務では、「AngularのことはTestBedが面倒を見てくれている」「探し方と操作の流儀はTesting Libraryに寄せる」と整理すると分かりやすいです。TestBedに慣れている人ほど、全部をfixtureから触りたくなりますが、そこを少し手放して screen とユーザー操作へ寄せると、テストの意図が読みやすくなります。
2. 最小の書き方(render→screen)
2-1. renderでテスト対象を描画する
Angular Testing Libraryの最小形は、render でコンポーネントを描画し、その結果をテストすることです。TestBedを直接組み立てるより、描画までの流れを短く書きやすくなります。
render は、対象コンポーネントをAngularのテスト環境上に描画し、必要なら imports、providers、componentInputs なども一緒に指定できます。これにより、「テストの前提条件」を1か所に集めやすくなります。特にStandaloneコンポーネントとの相性がよく、最小構成で描画しやすいのが利点です。
import { render, screen } from '@testing-library/angular';
import { ItemDetailComponent } from './item-detail.component';
it('商品未選択時のメッセージを表示する', async () => {
await render(ItemDetailComponent, {
componentInputs: {
item: null
}
});
expect(
screen.getByText('商品を選択してください。')
).toBeTruthy();
});
このコードでは、render の段階で Input を渡し、その後は画面に出ている文言だけを見ています。注意点は、描画設定を増やしすぎて“全部入りrender”にしないことです。まずはこのテストに必要な依存だけ渡すほうが、遅くなりにくく、壊れにくくなります。
2-2. screenで要素を探す(getBy/findBy)
Testing Libraryでは、描画後の要素探索は screen を起点にするのが基本です。fixtureやquerySelectorを直接使うより、「ユーザーが見つけられる形」で要素を探しやすくなります。
screen.getBy... は、今すぐ存在しているはずの要素を探すときに使います。一方、非同期で後から出る要素には findBy... を使います。この違いを守るだけでも、「待つべきところで待てていない」テストを減らしやすくなります。Testing Libraryでは、探し方そのものがテスト設計の一部です。
import { render, screen } from '@testing-library/angular';
import { SaveMessageComponent } from './save-message.component';
it('保存後にメッセージを表示する', async () => {
await render(SaveMessageComponent);
expect(screen.getByRole('button', { name: '保存' })).toBeTruthy();
// 保存後に非同期で出る想定
expect(await screen.findByText('保存しました')).toBeTruthy();
});
このコードでは、最初からあるボタンには getByRole を使い、後から出るメッセージには findByText を使っています。注意点は、「とりあえず全部findByにする」ことです。同期・非同期を区別せずに書くと、テストの意図がぼやけやすいので、「今あるはずか、後で出るはずか」を先に決めてからクエリを選ぶのが大切です。
3. クエリ戦略:role優先で壊れにくくする
3-1. getByRole/getByLabelText/getByTextの順
Testing Libraryで要素を探すときは、roleを最優先にし、それが難しければlabel、最後にtextを見るという順番にすると、壊れにくいテストになりやすいです。これはアクセシビリティにも沿った探し方です。
getByRole は、ボタン、リンク、見出し、テキストボックスのような「役割」で探せるため、見た目が少し変わっても意図が崩れにくいです。フォーム入力なら getByLabelText が自然です。getByText は便利ですが、装飾や文言変更の影響を受けやすい場面もあります。つまり、roleベースは“ユーザーが何として認識するか”に近いため、構造変更へ強いです。
実務では、「まずroleで探せないか」を最初に考えるだけで、querySelectorやCSS依存をかなり減らせます。チェックリストとしては、「ボタンはgetByRole」「入力欄はgetByLabelText」「補助的な文言確認だけgetByText」と決めると、チームで書き方も揃えやすいです。
3-2. data-testidを使う基準
data-testid は便利ですが、最初の選択肢ではなく、roleやlabelで表現しづらいときの最後の手段として使うのが基本です。なんでもtestidで探すと、結局は実装依存へ戻りやすくなります。
roleやlabelで探せるなら、そのほうがユーザー視点に近く、アクセシビリティとも整合が取れます。一方で、純粋に装飾用の要素、アクセシブルネームが取りづらい複雑な構造、同種の要素が大量に並ぶ場面などでは、testidが実務的な助けになることがあります。つまり、testidは悪ではなく、“使いどころを絞る”のが大事です。
判断基準としては、「ユーザーの言葉や役割で自然に探せるか」を先に考えるとよいです。もしそれが難しいなら、data-testid="loading-spinner" のように、意味が分かる名前で置くと読みやすくなります。逆に、すべての要素へ機械的にtestidを振ると、HTMLの保守コストだけが増えやすいです。
4. userEventでユーザー操作を再現する
4-1. click/type/clearの基本
ユーザー操作を再現するときは、fireEvent よりも userEvent を優先すると、実際の操作に近いテストを書きやすくなります。特に入力系では差が出やすいです。
userEvent は、単にイベントを1個飛ばすのではなく、人が操作したときに近い形でイベント列を発生させます。そのため、クリック、入力、削除などの挙動が現実に寄りやすく、実装依存のテストを減らしやすいです。Angularでも、「inputへ値を直接代入してdetectChanges」より、ユーザーが打ち込んだ流れを再現するほうが、振る舞いテストとして自然です。
import { render, screen } from '@testing-library/angular';
import userEvent from '@testing-library/user-event';
import { ItemFormComponent } from './item-form.component';
it('商品名を入力して追加できる', async () => {
const user = userEvent.setup();
await render(ItemFormComponent);
const input = screen.getByRole('textbox', { name: '商品名' });
const button = screen.getByRole('button', { name: '追加' });
await user.type(input, 'Angular Book');
await user.click(button);
expect(screen.getByText('Angular Book')).toBeTruthy();
});
このコードでは、ユーザーが商品名を入力して追加ボタンを押す流れを、そのまま再現しています。注意点は、userEvent は非同期になることが多いため、await を付け忘れないことです。付け忘れると、たまたま通るけれどCIで落ちるようなテストになりやすいです。
4-2. 非同期UIの待ち方(findBy/waitFor)
非同期で変わるUIは、findBy と waitFor を役割で使い分けると安定しやすいです。待ち方が雑だと、Testing Libraryでもflakyは普通に起きます。
後から表示される要素を待つなら、まずは findBy を優先します。これは「その要素が出てくるまで待つ」という意図が明確だからです。一方、要素そのものではなく、複数条件の成立や関数呼び出しのような期待を待つなら waitFor が向いています。全部をwaitForで包むより、待ちたい対象が要素ならfindByのほうが読みやすいです。
import { render, screen, waitFor } from '@testing-library/angular';
import userEvent from '@testing-library/user-event';
import { SaveButtonComponent } from './save-button.component';
it('保存後に完了メッセージを表示する', async () => {
const user = userEvent.setup();
await render(SaveButtonComponent);
await user.click(screen.getByRole('button', { name: '保存' }));
expect(await screen.findByText('保存しました')).toBeTruthy();
});
it('保存処理が呼ばれるまで待つ', async () => {
const saveSpy = jest.fn();
const user = userEvent.setup();
await render(SaveButtonComponent, {
componentProperties: {
onSave: saveSpy
}
});
await user.click(screen.getByRole('button', { name: '保存' }));
await waitFor(() => {
expect(saveSpy).toHaveBeenCalled();
});
});
この例では、画面に現れるメッセージには findByText を使い、関数呼び出しの確認には waitFor を使っています。注意点は、なんでもwaitForに押し込まないことです。waitForは便利ですが、曖昧に使うと「何を待っているか」が見えにくくなるので、まずはfindByで表現できないかを考えると整理しやすいです。
5. DI/Router/HTTPの扱い(最低限)
5-1. providersで依存を差し替える
Angular Testing Libraryでも、依存の差し替えはprovidersで行うのが基本です。ユーザー視点のテストをしつつも、外部依存まで本物でつなぐ必要はありません。
コンポーネントテストで確認したいのは、多くの場合「Serviceの中身」ではなく、「Serviceの結果を受けて画面がどう変わるか」です。そのため、Serviceはモックへ差し替えて、画面の振る舞いに集中したほうがテストの責務がはっきりします。Testing Libraryはユーザー視点を重視しますが、DIやprovidersの考え方はAngularそのものです。
import { render, screen } from '@testing-library/angular';
import { ItemListPageComponent } from './item-list-page.component';
import { ItemService } from './item.service';
it('モックServiceの一覧を表示する', async () => {
const mockItemService = {
getItems: () => [
{ id: 1, name: 'Mock Item', description: 'モックです' }
]
};
await render(ItemListPageComponent, {
providers: [
{ provide: ItemService, useValue: mockItemService }
]
});
expect(screen.getByText('Mock Item')).toBeTruthy();
});
このコードでは、Serviceだけを差し替えて画面表示を確認しています。注意点は、モックを増やしすぎて「何をテストしているか」が曖昧になることです。コンポーネントテストでは、外部依存はモックしつつ、画面の振る舞いは本物として見る、というバランスを保つと読みやすくなります。
5-2. RouterやHTTPが絡むときの割り切り
RouterやHTTPが絡むときは、全部を本物でつなごうとせず、テストの責務に応じて割り切ることが大切です。ここを欲張ると、Angular Testing Libraryでも重くて壊れやすいテストになりがちです。
たとえば、画面遷移ボタンの存在やクリック後の表示変化を見たいだけなら、Router全体の統合テストにしなくても十分な場合があります。HTTPも同様で、コンポーネントの表示を見たいならServiceモックで足りることが多いです。一方で、Service自体のHTTP処理を見たいなら、それは別のServiceテストへ寄せたほうが責務がきれいです。
実務では、「このテストはUIの振る舞いを見るのか」「ルーティングや通信まで含めて見るのか」を先に決めると迷いにくいです。最低限の割り切りとしては、「コンポーネントテストは画面中心、HTTPはService側で、Routerは必要最小限の設定だけ」という考え方が扱いやすいです。全部を一つのテストで確認しようとするほど、壊れやすくなります。
6. まとめ
Angular Testing Libraryの価値は、TestBedの知識を捨てることではなく、ユーザー視点のテストへ寄せやすくすることにあります。render で描画し、screen で探し、getByRole を優先し、userEvent で操作する流れに揃えるだけでも、実装詳細へ寄りすぎたテストをかなり減らせます。
特に重要なのは、roleベースで探すことと、同期・非同期の待ち方を分けることです。ボタンや入力欄をroleやlabelで探せると、DOM構造やクラス名に依存しにくくなります。また、後から出る要素には findBy、条件待ちは waitFor と使い分けると、flakyも減らしやすくなります。
AngularらしいDIやprovidersはそのまま活かしつつ、テストの書き方だけをユーザー視点へ寄せるのが、Angular Testing Libraryの実務的な使い方です。壊れにくいテストは、複雑な道具を増やすことではなく、「ユーザーが何を見て、何を操作するか」をテストコードにそのまま書ける状態から始まります。
7. 参考リンク
- Angular Testing Library
https://testing-library.com/docs/angular-testing-library/intro/ - Testing Library Queries
https://testing-library.com/docs/queries/about/ - Testing Library user-event
https://testing-library.com/docs/user-event/intro/ - Angular Testing Guide
https://angular.dev/guide/testing