Open16

フロントエンドテストの基礎

夜猫夜猫

テストの基礎

テストの種類

英語 テスト名称 概要
E2E エンドツーエンドテスト システム全体が意図通りに動作するかを確認するテスト。ユーザー視点での一連の操作をシミュレートします。
Integration Test 結合テスト 複数のモジュールやコンポーネントが正しく連携して動作するかを検証するテスト。各部分が統合された際の不具合を発見します。
Unit Test 単体テスト 個々の機能やモジュールが仕様通りに動作するかを確認するテスト。通常はコードの最小単位である関数やメソッド単位で行われます。
Static Test 静的テスト プログラムの実行を伴わず、コードや設計書をレビューすることで潜在的な欠陥やバグを見つけるテスト。代表例として静的コード解析があります。

使用されるライブラリ

  • E2E: Playwright cypress
  • 統合テスト / 単体テスト: Testing Library / Jest / Vitest
  • 静的テスト: TypeScript Prittier

→ 最近は,Playwright Vitestが使われるようになっている.

テストのメリット・デメリット

上に書かれているものに従って,テストのコストがかなり増える.

テストタイプ メリット デメリット
E2E システム全体の動作を確認できる テストが重く、余計なコードが増える.リリースまでの期間が増える
統合テスト モジュール間の連携を確認でき、不具合の早期発見に役立つ 設計が複雑で、開発・保守のコストが高い
単体テスト 各コンポーネントを早期に検証でき、開発が進みやすい 単一コンポーネントのみを対象にするため、全体の動作保証が難しい
静的テスト 実行前にコードの品質を検証でき、バグを早期に発見できる 実行環境での不具合は検出できず、誤検知や無関係な警告が出ることがある

→ テスト戦略を考える必要がある.テストをやらないのも1つの戦略である.

なぜテストをするのか

  1. 開発者のコードへの自信をつける
  2. バグの早期発見に繋がる
  3. コンポーネントの責務が分離されやすい
夜猫夜猫

Reactでのテスト

create-react-appで生成される,デフォルトのテストの中身を見る


test('renders learn react link', () => {
  // コンポーネントのレンダリング
  render(<App />);

  // レンダリングされた結果に,learn reactを含む要素を取得 
  const linkElement = screen.getByText(/learn react/i);

  // 取得した結果,レンダリング結果にあるかどうかを確認
  expect(linkElement).toBeInTheDocument();
});

このテストは,デフォルトで設定されているコンポーネントのレンダリング結果をテストしている.実際にレンダリングした中に,「learn react」の文字列があるかどうかを示している.

夜猫夜猫

テスト駆動開発

開発方法

  1. 失敗するテストを記載する
  2. 1で作成したテストを通るようにプログラムを修正
  3. リファクタリングを行って,プログラムを良いものにする

コンポーネントにPropsを渡すテストを記載

  1. コンポーネントにPropsを渡すテストを記載 → a failing test
  2. Propsの型を定義し,Propsを渡す
  3. Propsを利用した実装を行う

JestのWatchモードコマンド

  • a: 全てのテストを実行してくれる
  • f: 失敗したテストのみを実行してくれる
  • o: 直近で変更されたファイルのみをテストする
  • p: 正規表現で対象のテストファイルを指定できる
  • t: 正規表現でテスト名を指定 → test名がアルファベットである場合のみ
  • w: 別のテストモードにする際に利用

→ 基本的には,テスト名は英語で記載することにする.

Jestの記載方法

  • describe: テストのグルーピングを行う.ネストさせることも可能である.

カバレッジテスト

テストがコンポーネントの分岐を網羅しているかどうかを検証する方法である.

現実的には100%は難しいので,一般的には80%を超えたらよしとする場合がある.

  • State: 定義された文の網羅率
  • Branch: if else switch 三項演算子の網羅率
  • Func: 関数の網羅率
  • Lines: 実行された行数

基本的な記載:

npm test -- --coverage

// 全部のファイルを見る (src配下の)
--watchAll

// 特定のファイルのみを対象にしたい
--collectCoverageFrom='src/components/**/*.{ts,tsx}'

// カバレッジの閾値をつける

カバレッジの状態については,package.jsonに追記する

  "jest": {
    "coverageThreshold": {
      "global": {
        "statements": 90,
        "branches": 80,
        "functions": 90,
        "lines": 90
      }
    }
  }
夜猫夜猫

React Testing Library Queries

クエリとは

ページ上の要素を取得するために,Testing Libraryが提供しているメソッドになる.

https://testing-library.com/docs/queries/about

ByRole

  • エレメントの存在確認をする際に利用するもの
  • role: ここに指定した役割を持つ,エレメントが存在しているかを理解できる
  • オプション: 特定の名前を持つエレメントの存在を確認したい場合等

roleの具体的な内容については,以下のWebサイトを参考にする

https://www.w3.org/TR/html-aria/#docconformance

クエリの優先度について

  1. 視覚・マウスで使用するエレメント ByRole → ByLabelText → ByPlaceholderText → ByText → ByDisplayValue
    1. Role: エレメントの取得
    2. LabelText: ラベル属性のテスト
    3. Placeholder: placeholderのテスト → 視覚障害者等の方に不親切なので,優先度低め
    4. Text: テキストがあるかどうか
    5. DisplayValue: Inputの初期値等の検証に利用する → デフォルト値の設定をした際の検証?
  2. セマンティック要素(altやtitle属性): ByAltText → ByTitle
    1. AltText: imgタグについている,alt属性の値を調査
    2. Title: title属性の内容を検証 spanやsvgタグに使用されている属性?
  3. TestID
// inputとラベル名の対応をテスト
const nameElement = screen.getByRole("textbox", {
	name: "お名前",
 });
expect(nameElement).toBeInTheDocument();

// ラベルとして存在しているかをテスト
const nameElement2 = screen.getByLabelText("お名前");
expect(nameElement2).toBeInTheDocument();

TestIDの使い所

  • 修正の前と後でタグ等の属性を変更する際に利用する?
  • 改修後は,そのTestIDを用いたテストをクエリ優先度が高いものに変更してテストを書き直す

クエリの種類について

  • get: 1つの要素について検証
  • getAll: 取得できる全ての要素について検証
  • query: 要素がないことを検証

getAll

  • toHaveLength: 個数のチェックで利用する

queryBy

  • 使い所: ログインしていたら要素を表示して,未ログインであれば要素を表示しないことを検証する

// ログイン状態の際に,ログアウトのボタンが出ていないか
test("logout button is not render", () => {
    render(<Skills skills={skills} />);
    const logoutButton = screen.queryByRole("button", {
      name: "ログアウト",
    });
    expect(logoutButton).not.toBeInTheDocument();
 });

findBy

  • 使い所: 非同期処理を記載するとき
  • 第3引数に待つ時間を指定すると,その時間待つようになる
  • APIを叩く際にこのメソッドを用いる
// 非同期で状態が変更される場合のテスト
test("logout button is eventually rendered", async () => {
    render(<Skills skills={skills} />);
    const logoutButton = await screen.findByRole(
      "button",
      {
        name: "ログアウト",
      },
      {
        timeout: 1600,
      }
    );
    expect(logoutButton).toBeInTheDocument();
 });

テストでのデバック方法

  • テスト中のDOMの要素を見ることができる screen.debug();
  • logRole関数によって,コンポーネントに含まれるRoleを確認することができる
  • Chrome拡張機能: Testing Playground

→ 使い終わった後で,きちんと削除すること

test("logout button is eventually rendered", async () => {
    const view = render(<Skills skills={skills} />);
    logRoles(view.container);
});
夜猫夜猫

ユーザーイベントテスト

ユーザーの入力やクリックイベントに対するテストを記載

https://testing-library.com/docs/user-event/intro

イベント

  • click
  • type
  • hover
  • un hover

testing-libraryのuser-eventを用いることによって実装できる

  1. テストの関数を非同期に設定
  2. user-eventのsetup関数を呼ぶ
  3. ユーザーイベントを発生させるエレメントを特定
  4. エレメントに対して,ユーザーイベントを実行
  5. テスト検証を行う

クリックイベントに関するテスト記載


import userEvent from "@testing-library/user-event";

test("renders a count of 1 after clicking the increment button", async () => {
    const user = userEvent.setup();
    render(<Counter />);
    const incrementButton = screen.getByRole("button", {
      name: "Increment",
    });

		// クリックイベントを発火させる
    await user.click(incrementButton);
    const countElement = screen.getByRole("heading");
    expect(countElement).toHaveTextContent("1");
  });

入力を行う

  test("renders a count of 10 after clicking the set button", async () => {
    const user = userEvent.setup();
    render(<Counter />);

    const amountInput = screen.getByRole("spinbutton");
    // Input要素に対して,5を入力する
    await user.type(amountInput, "5");
    expect(amountInput).toHaveValue(5);

    const setButton = screen.getByRole("button", {
      name: "Set",
    });
    // Setボタンのクリックを実行
    await user.click(setButton);
    const countElement = screen.getByRole("heading");
    expect(countElement).toHaveTextContent("5");
  });

カスタムHooksのテスト記載

  • カスタムHooksについては,関数コンポーネント内でしか呼べない → 直接テストに記載できない.
  • renderHookを使う必要がある.
  • act: hooksの更新関数等を叩く場合に利用
describe("useCounter", () => {
  test("should render the initial count", () => {
	  // 初期値無しでの実行
    const { result } = renderHook(useCounter);
    expect(result.current.count).toBe(0);
  });

  test("Initial Count test", () => {
	  // 初期値を設定する場合
    const { result } = renderHook(useCounter, {
      initialProps: { initialCount: 10 },
    });
    expect(result.current.count).toBe(10);
  });
  
  test("Increment the count", () => {
    const { result } = renderHook(useCounter);
    act(() => {
      result.current.increment();
    });
    expect(result.current.count).toBe(1);
  });
});
夜猫夜猫

WebAPIテスト

msw

  • mock service worker
  • APIの開発を行われていない場合は,mswを使って,フロントエンドのテストを実行する

mockAPIをテストに反映させる方法

  1. mockサーバーの記述を行う
  2. setupTests.tsに,mockサーバーの記述を行う
  3. テストの中で記載する

同じAPIでも別の挙動を検査したい

  test("renders error", async () => {
	  // mockサーバーの挙動を変更
    server.use(
      rest.get(
        "https://jsonplaceholder.typicode.com/users",
        (req, res, ctx) => {
          return res(ctx.status(500));
        }
      )
    );
    render(<Users />);
    const error = await screen.findByText("Error");
    expect(error).toBeInTheDocument();
  });
夜猫夜猫

Vitest導入

Vitest

  • Jestと比較するとテストが早く完了する
  • Jestと互換があるので移行しやすい
  • happy-dom: 高速で動く

設定関連

  • 必要なものをインストール

  • tsconfig.app.jsonを編集 → typesにvitestを設定すること

  • vite.config.tsを編集 → describe やtestの関数をVitestから一括で設定する

  • ESLintの導入: 静的構文チェック

  • Prettire コードフォーマッター

    • Hasky pushする際にフォーマットを適用できる
    • lint-stages commit前にフォーマッターを適用できる
夜猫夜猫

テストの目的と障壁

テストの種類について

  • 関数単体テスト SPAやBFF等の関数単体をテストする.これらの関数の組み合わせでアプリができている
  • UIコンポーネント単体テスト UIコンポーネント毎に,Webアクセシビリティや単体テストを行う
  • UIコンポーネント結合テスト 複数のコンポーネントを含めた動作のテストを行う.mockサーバーや操作を伴う非同期処理のテストが行われる.
  • UIコンポーネントビジュアルリグレッションテスト CSSの指定に伴うコンポーネントの検証を行う
  • E2Eテスト ブラウザ固有のAPIの検証や,画面をまたぐテストを行う.DBサーバーや外部ストレージサーバーに接続して,アプリ全体の挙動をテストする.

テストで使用するライブラリ・ツールについて

  • Jest テスティングフレームワーク.テストランナー
  • Playwright ヘッドレスブラウザを含んだテスティングフレームワーク,テストランナー
  • reg-suit ビジュラルリグレッション・テスティングフレームワーク
  • Storybook UIコンポーネントエクスプローラー

テストの書くことによるメリット

  • プロダクトへの信頼のため
    • バグを含めてリリースしてしまうと,プロダクトを用いている事業に影響が出てしまう.リリース前に,自動テストをすることで,バグの混入を減らせる可能性がある
  • コードの品質の向上
    • リファクタリングする際に,既存のロジックを満たす保証ができる.モジュールの共通化等のリファクタリングを行う際に,テストによって挙動が正しいことを簡単に確認できる.この環境があることによって,積極的にリファクタリングできる環境が提供される.
    • テストコードを書くことによって,自身の実装を見直す機会を提供する.テストコードが書きずらい場合は,対象のコードが肥大化している可能性がある.そのような,見直す機会を与えてくれる.
  • 開発者同士の協力を円滑にする
    • テストを記載することで,対象の実装に対するドキュメントの役割も果す.テストを記載する際には,そのテストで行うことを端的に記載する.その記載によって,対象のコンポーネントがどんな機能を提供していて,どのような挙動が正しいのかを把握する際に,手助けしてくれる.
    • 要件を満たしているのかを確認できる.特定のタスクに対して,要件が記載されている文書とテストを見比べる.そこで差分があれば,素早くレビューの指摘を行い,改善活動を行うことが出来る.
  • リグレッションを防ぐ
    • リグレッションとは,新しい変更や修正が改修以前の動作に対して悪影響を与えること.デグレードと同じ意味を持つ?
    • リグレッションテストによって,既存の正しい挙動を定義しておき,改修によってプロダクトに対して思わぬ悪影響を与えていないかをテストする.レビューを通して,他者の検査を通っていても,思わぬ場所でバグを引き起こす可能性もあります.報告されたバグに対して,とある関数を直したとします.特定の画面ではバグは直りましたが,別画面で同じ関数を利用しており,その変更によって別画面の挙動がおかしくなっていました.モダンフロントエンド開発では,小さいコンポーネントを結合して1つの画面を作るので,影響が思わぬところに出てしまう可能性があります.開発者側では他の画面への影響を考慮できておらず,そのままリリースしてしまうこともあるかと思います.
夜猫夜猫

テスト手法とテスト戦略

テストの範囲について

機能を提供するために必要なもの

  1. ライブラリが提供する関数
  2. ロジックを担う関数
  3. UIを表現する関数
  4. Web APIクライアント
  5. APIサーバー
  6. DBサーバー

テストの大まかな種類

  • 静的解析テスト
  • 単体テスト
  • 結合テスト
  • E2Eテスト

静的解析テスト

  • 使用技術: TypeScript・ESLint・Prettier
  • モジュール内部またはモジュール間で,変数の型や開発チーム内で決めたコーディングルールに従っているかをテストします.これにより,細かいルールに従っていないコードを検知し,開発者が直す機会を環境を提供します.
  • この開発ルールに則っていないコードであれば,pushをさせないようにするライブラリ等があります.それを用いれば,最低限のルールを指摘する時間をレビュワーが取らずに済む可能性があります.その浮いた時間を用いて,よりプロダクトの品質を担保するレビューを行うことができます.

単体テスト

  • 使用技術: Jest・Vitest・React Testing Library etc…
  • 最も基本的なテストであり,モジュール単体の挙動をテストする.モジュール単体の挙動をテストする.画面を通しての検証ではなく,コードでの検証なのでコマンドを実行するだけで,挙動の確認ができる.また,コーナーケースなどの滅多に発生しないような挙動のテストを行うことで,モジュールが細かな要件を満たしているのかを,コードによって検証できる.

結合テスト

  • 使用技術: 単体テストと同様
  • モジュール同士を繋げて提供できる挙動をテストする.モジュール単体では検証できない,モジュール間での連携を伴う挙動のテストを行う.参考書籍では,セレクトボックスを用いた検索機能を例にして解説されていました.その機能を提供するために必要な一連の挙動を,結合テストでテストします.粒度については,提供機能全体でも一部の機能を切り出して狭い範囲で結合するテストもあると言われていました.

E2Eテスト

  • 使用技術: Playwright etc…
  • ヘッドレスブラウザを用いて,アプリケーション全体の挙動をテストする.実際にユーザーが使う動作をコードで定義し,想定している機能を提供できているのかをテストする.他のシステム(外部ストレージやDBサーバー)との連携が,想定通りに動いているかどうかをテストする.

テストの目的によって,さらに細分化できる

  • 機能テスト 開発した機能に不具合がないかを検証
    • 開発した機能に不具合がないかを検証で,こちらのテストが大半を占めることになる.
    • 例) ボタンを押したら関数が呼ばれる.特定の動作をしたらボタンが活性化される.ブラウザのストレージに値が入る
  • 非機能テスト
    • アクセシビリティに関する項目について検証
    • 例) チェックボックスにチェックできる.アクセシビリティ違反がないか
  • リグレッションテスト 改修によって想定外の不具合がないかを検証
    • ビジュアルリグレッションテスト 見た目のリグレッションがないかをテストする.画面のキャプチャ同士を比較して,リグレッションがないかを検証.
    • 例) ボタンのCSSが想定通り適用されているか.画面全体にリグレッションがないか

テスト戦略

テスト戦略とは,テストの種類で挙げた各項目をどのくらい実装するかである.

E2Eはプロダクトが提供したい機能をテストする.E2Eテストを作り込んで,プロダクトの品質を上げれば良いと考えられる.しかし,E2Eテストはかなり実装コストがかかり,その後のメンテナンスも大変である.また,他のテストに比べると実行時間もかかる.

テストにかけられる時間も開発チームで限られてきますので,どのテストにどのくらいのコストをかけるのかは重要です.この検討を行うのに参考になるのが,テスト戦略モデルになります.プロジェクトの状況によって,選択すべき戦略が異なります.

テストピラミッド型

コストの低い単体テストを多く書くことで,より安定した費用対効果の高いテスト戦略である.

テストコードがないプロダクトにて,リリース済みの機能をテストするためのリグレッションテストを記載していく.その後,結合テスト等を段階的に書いていく.

テスティングトロフィー型

結合テストに,最も大きな比重を置くべきであるというテスト戦略である.

UI操作を伴う機能は,複数のコンポーネントを組み合わせて提供されます.そのため,ユーザー操作から考えて,ある程度小さい単位の結合テストを充実させることが重要であるとする考え方になります.

モデル以外にも,E2Eテストをテスト用のステージング環境を構築してやるのか,テストコンテナを用意してそれをCIで起動してテストを行う手法の検討もあるそうです.

夜猫夜猫

単体テスト

テストの構成要素

  • テストのまとまりを記述: test・describe
  • アサーション: expect
  • マッチャー: toBe等のライブラリ側で提供している関数

アサーションは,マッチャーに指定された想定している値の種類と具体的な値が一致しているかを検証します.

マッチャー

  • 真偽値 toBeTruthy, toBeFalsy
  • 数値 toBe, toBeGreaterThan, toBeFreaterThanOrEqual, toBeLessThan, toBeCloseTo
  • 文字列 toContain, toMatch, toHaveLength
  • 配列 toContainEqual, arrayContaining
  • オブジェクト toMatchObject, toHaveProperty

非同期処理のテスト記載

  • Promiseを用いて記載
  • async/awaitを用いて記載

テストとして,通るべきアサーションを数を定義

  • expect.assertions() 引数にそのテストで検証するアサーションの数を指定する.
夜猫夜猫

モック

実際のWeb APIや外部サービスとの連携では,テスト環境の構築が大変な場合がある.その場合は,サービスそのものではな「サービスで想定される挙動を定義した関数」を用意すれば良いです.それをモックと呼びます.

用語の整理

  • モック 実際のWeb APIや外部サービスとの連携では,テスト環境の構築が大変な場合がある.その場合は,サービスそのものではな「サービスで想定される挙動を定義した関数」を用意すれば良いです.それをモックと呼ぶ.
  • スタブ テスト対象に入力を与えるものであり,定められた値を返却する.
  • スパイ テスト対象の入出力を監視するものであり,呼び出された回数や実行時の引数を記録する.
  • フィクスチャー レスポンスを再現するテスト用データのこと.

Jestでのモックの使用例

  • 第一引数: スタブを実装する関数名
  • 第二引数: 関数の実装
// スタブの実装
jest.mock("./sayHello", () => ({
	sayHello: (name: string) => `Hello ${name}`,
}));

Web APIのモック

Webアプリケーションでは,サーバーからAPIからデータを受け取って表示する機能がたくさんあります.それを,実際のAPIからではなく,スタブを作成してテストを行います.

  1. fetchする内容を定義したファイルをimportする
  2. 定義されている関数のスタブを作成(jest.spyOnで実装が可能)
  3. テストの実装

レスポンスが固定の場合

// 成功する挙動の定義
jest.spyOn(FetchObject, "getUserData").mockResolvedValueOnce({
	id: "xxxxxxx",
	age: xxx,
	email: "xxxx@xxx.com",
	icon: xxxxxx
});

// 失敗する挙動の定義
jest.spyOn(FetchObject, "getUserData").mockRejectValueOnce(httpError);

レスポンスが固定ではなく,条件によって返却値が異なる場合

1つも該当するデータがない場合はnullを返却し,データがある場合は型定義に従ったデータが返却されるような場合です.

  1. フィクスチャーを定義
  2. スタブの返却値をフィクスチャーに指定
  3. テストの実装
// フィクスチャー定義
export const getUserData: User = {
	user: [
		{
				id: "xxxxxxx",
				age: xxx,
				email: "xxxx@xxx.com",
				icon: xxxxxx
		},
		...
	],
} 

// スタブ関数の定義
function mockGetUserData(status = 200) {
	// 失敗した場合の返却値
	if (status !== 200) {
		return jest.spyOn(FetchObject, "getUserData").mockRejectValueOnce(httpError);
	}
	// 成功した場合の返却値
	return jest.spyOn(FetchObject, "getUserData").mockResolvedValueOnce(getUserData);
}

// テストの実装
test("ユーザーデータの取得成功", async () => {
	mockGetUserData();
})

test("ユーザーデータの取得失敗", async () => {
	mockGetUserData(500);
})

スパイによる関数実行の監視

  • テスト対象で関数が呼ばれたのか,何回呼ばれたのか
    • toBeCalled
    • toHaveBeenCalledTimes
    • toHaveBeenCalled
  • 引数にどんな値が渡ってきたのか
    • toHaveBeenCalledWith
    • expect.objectContaining

バリデーションを伴うテスト

  1. バリデーションの挙動を考慮したモック関数の作成
  2. 送信データを動的に生成できる,ファクトリー関数を作成
  3. テスト実装

テストの前後で共通する処理を実行したい場合

beforeAll(() => {})
beforeEach(() => {})

afterEach(() => {})
afterAll(() => {})

夜猫夜猫

UIコンポーネントテスト

UIコンポーネントは以下の役割を持つ

  • Web APIからデータを取得し表示すること
  • ユーザーからの操作を受ける

以上を,意図した通りの機能を提供しているかをテストする.

必要なライブラリ

  • jest-environment-jsdom テスト実行環境でDOM操作を伴うテストを行えるようにする.
  • testing-library UIコンポーネントのレンダリングやインタラクションを与えることができる
    • @testing-library/react Reactで実装しているテストを記載する
    • @testing-library/jest-dom Jestの拡張を行い,UIコンポーネントテストに便利なマッチャーを提供
    • @testing-library/user-event ユーザー操作に近いシミュレーションを行うために必要

UIコンポーネントテスト

  1. テスト対象のコンポーネントをレンダリング
  2. 任意のDOMを取得
  3. DOM操作を行う
  4. アサーションを記載

テスト対象のコンポーネントをレンダリング

test("レンダリング", () => {
	render(<SampleCompooent name="sample">);
});

任意のDOMを取得

test("レンダリング", () => {
	render(<SampleCompooent name="sample">);
	
	// 任意のDOMを取得
	const textElm = screen.getByText("sample");
	const buttonElm = screen.getByRole("button");
});

アサーションを記載

test("レンダリング", () => {
	render(<SampleCompooent name="sample">);
	
	const textElm = screen.getByText("sample");
	const buttonElm = screen.getByRole("button");
	
	// テストしたい項目を記載
	expect(textElm).toBeInTheDocument();
	expect(buttonElm).toHaveTextContent("sample");
});

DOM操作を行う

クリックイベントを発火させて,コンポーネントに渡したコールバック関数が実行されたかを検証する.

test("レンダリング", () => {
	// モック関数を生成
	const mockFn = jest.fn();
	render(<SampleCompooent name="sample" onClick={mockFn}>);
	
	// クリックイベントの実行	
	fireEvent.click(screen.getByRole("button"));
	
	// モック関数が呼ばれたかどうか 
	expect(mockFn).toHaveBeenCalled();
});

テスト記載の例

  • 一覧表示:
    • 要素取得: getAllByRole, within, queryByRole
    • アサーション: toHaveLength, toBeInTheDocument, toBeNull
  • リンクの要素
    • アサーション: toHaveAttribute
  • フォーム要素
    • 要素取得: getByRole, getByDisplayValue, getByPlaceholderText(パスワードの入力欄の特定で利用)
    • アサーション: toBeChecked, toBeEnabled
    • DOM操作: useEvent.setup → typeメソッドの実行

アクセシブルネーム

支援技術が認識するノードの名称であり,aria-xxxのような要素を指すものになる.ユーザーインターフェイス要素の名前のことで,要素の目的や意図を伝えることができる.

ページ内で固有のものである必要がある.React 18ではIDを使う際に,便利に生成できるHooksを用意してくれている.

→ これを付与することで,ロールが適用される場合があり,テストしやすくなるらしい…

ロールやアクセシブルネームを確認

test("レンダリング", () => {
	const { container } = render(<SampleCompooent name="sample" onClick={mockFn}>);
	
	logRoles(container);
});

フォームに対するインタラクションを関数化して,再利用する

表示分岐を含むテストでは,同じインタラクションをテスト内で記載する傾向がある.同じ記載を繰り返す場合は,1つの関数にまとめて,再利用することを検討してほしい.

非同期処理を含むテスト

  1. テストすべき項目の検討
  2. WebAPIのモックを作成
  3. フォーム入力の挙動を関数化して再利用可能に
  4. 各テスト項目について実装
    1. 成功レスポンス
    2. 失敗レスポンス
    3. バリデーション

スナップショットテスト

ある時点でのレンダリング結果をファイルとして保存しておき,その結果とテスト実行時点でのレンダリングに差分がないかをテストする.

夜猫夜猫

カバレッジレポート

カバレッジレポートとは

テストで記載した内容が,テスト対象のコンポーネントのどの割合実行したのかを計測し,レポートとして出力したもの

名前 機能
File テスト対象のファイル名称
Statements 全ての命令のうち,どの程度網羅しているのか
Branch 全ての分岐を,どの程度網羅しているのか
Functions 定義されている関数が,どの程度実行されたか
Lines 全ての行を,どの程度網羅しているのか
Uncovered Line テストで網羅できていない行がどこなのか
夜猫夜猫

Webアプリケーション結合テスト

Contextを用いたGlobal UIの結合テスト手法

方法1: テスト用コンポーネントを用いて非同期でテストを実行

  1. テスト用の最低限のコンポーネントを定義
  2. テスト用のコンポーネントをProviderで囲って,レンダリング
  3. アサーション: 表示されていないテストの実行
  4. クリックイベント(トーストを表示するためのイベント)を発火
  5. アサーション: トーストの要素が表示されているか

方法2: default値の設定で,コンポーネントのレンダリング結果をテスト

  1. トーストのデフォルト値を定義
  2. デフォルト値を指定して,レンダリング
  3. アサーション: デフォルトの値が表示されているかを検証

URLのpathを用いた結合テスト

next-router-mockを利用して,テスト実行環境でNext.jsのルーティングに関わるテストを行える

  1. setCurrentUrlにて,URLの指定を行う
  2. コンポーネントのレンダリング
  3. テストしたエレメントの取得
  4. アサーションの記載

Nextjs Routerの操作結合テスト

セレクトボックスの内容によって,URIを変更するケースについてテストを記載する

初期表示テスト

  1. setCurrentUrlにて,URLの指定を行う
  2. コンポーネントのレンダリング
  3. テストしたエレメントの取得
  4. アサーションの記載

→ 3までをsetup関数として定義することで,再利用可能

インタラクションテスト

  1. 初期表示テストに,非同期関数でselectOptionsを実行して,特定のラベルを持つ選択肢を押下した際のイベントを定義
  2. 1で定義したsetup関数の実行
  3. next-router-mockの toMatchObject を用いてクエリの内容をテスト
  4. 1で定義したイベントを発火させる
  5. クエリの内容をテスト
  6. 異なるイベントを発火させる
  7. クエリの内容をテスト

Web APIレスポンスのモック

Web APIのモックには,MSWが用いられる.特定のAPIに対するリクエストを受け付け,そのレスポンスを任意の値にすることができる.

→ jsdomではfetchに対応していないため,polyfillであるwhatwg-fetchが必要になる?

  1. setupServer を用いてテスト向けのサーバーのセットアップを行う
  2. セットアップ関数に,インタラクション用の関数を定義
  3. アサーションを定義
夜猫夜猫

UIコンポーネントエクスプローラー

StoryBook

https://storybook.js.org/

StoryBook

  • 実装したコンポーネントを,Web上で確認することができる
  • コンポーネントが一覧で見れるので,デザイナーやプロダクトオーナーとも共有がしやすい
  • 昨今,テストツールとして機能が拡充されている

StoryBookでの機能

  • コンポーネントを一覧で見れる
  • Controls Propsを書き換えて,リアルタイムにデバッグできる
  • Actions Propsに渡されたイベントハンドラーがどのように呼び出されたのかをログで見ることができる
  • Decorator Storyをレンダリングする際の,共通で記載したい処理を指定できる
  • Play Function 入力やクリックイベント等のインタラクションを登録できる