Dev Study
← 解説「コンポーネントのテスト — TestBedで部品を組み立てて表示を検証」に戻る

サンプルコードで身につける: コンポーネントのテスト — TestBedで部品を組み立てて表示を検証

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1コンポーネントテストの基本形

組み立て、描画、検証の3ステップが基本形です。standalone コンポーネントは imports に登録し、createComponent で生成、detectChanges で描画してから DOM を確認します。この形をまず型として覚えましょう。

import { TestBed } from '@angular/core/testing';
import { Header } from './header';

it('タイトルが表示される', () => {
  // 1. 組み立て: standalone コンポーネントは imports に入れる
  TestBed.configureTestingModule({ imports: [Header] });
  const fixture = TestBed.createComponent(Header);

  // 2. 描画: これを呼ぶまでDOMは空
  fixture.detectChanges();

  // 3. 検証: 実際のDOMを取り出して文言を確かめる
  const el: HTMLElement = fixture.nativeElement;
  expect(el.querySelector('h1')?.textContent).toContain('マイアプリ');
});

2setInput で Input を流し込む

input() で定義した Input には fixture.componentRef.setInput で値を渡せます。値を変えたら detectChanges を呼び直すことで、Input の変化に応じた表示の切り替わりを検証できます。

// user-badge.ts(テスト対象)
// export class UserBadge {
//   name = input.required<string>();
//   isAdmin = input(false);
// }

it('管理者には[管理者]バッジが付く', () => {
  TestBed.configureTestingModule({ imports: [UserBadge] });
  const fixture = TestBed.createComponent(UserBadge);

  // Input を外から流し込む(親コンポーネントは不要)
  fixture.componentRef.setInput('name', '佐藤');
  fixture.componentRef.setInput('isAdmin', true);
  fixture.detectChanges();

  const el: HTMLElement = fixture.nativeElement;
  expect(el.textContent).toContain('佐藤');
  expect(el.textContent).toContain('管理者');
});

3ボタンクリックで表示が変わることを検証する

nativeElement から取得したボタンの click() を呼べば、ユーザーの操作を再現できます。操作のあとに detectChanges を呼び、変化後の表示を検証する流れです。

// counter.ts(テスト対象)
// export class Counter {
//   count = signal(0);
//   increment() { this.count.update((c) => c + 1); }
// }

it('ボタンを押すとカウントが増える', () => {
  TestBed.configureTestingModule({ imports: [Counter] });
  const fixture = TestBed.createComponent(Counter);
  fixture.detectChanges();
  const el: HTMLElement = fixture.nativeElement;

  // ユーザーの操作を再現: ボタンをクリック
  el.querySelector('button')?.click();
  // 操作後は再描画してから検証する
  fixture.detectChanges();

  expect(el.textContent).toContain('カウント: 1');
});

4リスト表示の件数を検証する

@for で並べたリストは querySelectorAll で要素を数えて検証します。0件のときに空メッセージが出るか、といった分岐の確認も同じ道具立てでできます。

it('渡した商品の数だけ行が表示される', () => {
  TestBed.configureTestingModule({ imports: [ProductList] });
  const fixture = TestBed.createComponent(ProductList);

  fixture.componentRef.setInput('products', [
    { name: 'りんご', price: 100 },
    { name: 'みかん', price: 80 },
  ]);
  fixture.detectChanges();

  const el: HTMLElement = fixture.nativeElement;
  // @for で描画された行の数を数える
  expect(el.querySelectorAll('li').length).toBe(2);
});

it('0件のときは空メッセージが出る(境界値は画面でも試す)', () => {
  TestBed.configureTestingModule({ imports: [ProductList] });
  const fixture = TestBed.createComponent(ProductList);
  fixture.componentRef.setInput('products', []);
  fixture.detectChanges();

  const el: HTMLElement = fixture.nativeElement;
  expect(el.textContent).toContain('商品がありません');
});

5detectChanges を忘れるとどうなるか

createComponent した直後の DOM は空で、setInput しただけでは画面に反映されません。detectChanges の呼び忘れは初学者が最初に踏む落とし穴なので、失敗する形を一度見ておきましょう。

it('detectChanges を忘れた例(これは失敗する)', () => {
  TestBed.configureTestingModule({ imports: [Header] });
  const fixture = TestBed.createComponent(Header);
  const el: HTMLElement = fixture.nativeElement;

  // detectChanges() を呼んでいないので、DOMはまだ空っぽ
  // expect(el.querySelector('h1')?.textContent).toContain('マイアプリ');
  // → Expected: 'マイアプリ' / Received: undefined で失敗
});

// 覚え方:
//   ・createComponent した直後 → まず detectChanges()
//   ・setInput やクリックで状態を変えた後 → もう一度 detectChanges()
// 「表示が変わらない」と思ったら、まずこれを疑う