【TypeScript】ユニットテストにおける型のモック手法 - 型安全なテストの実践

【TypeScript】ユニットテストにおける型のモック手法 - 型安全なテストの実践

2024-11-10

2024-11-10

概要

TypeScriptでユニットテストを行う際、外部依存を排除するためにモック(Mock)を使用することが一般的です。特に、API呼び出しやデータベースアクセスといった依存を含む関数をテストする際にモックは欠かせませんが、TypeScriptではモックにも型安全性を求められます。型定義に基づいたモックの作成方法と、Jestを使用した具体的な実装例を通して、TypeScriptの型安全なモック手法を紹介します。

モックとは?

モックは、テスト対象の関数やメソッドから外部依存を取り除き、テストに必要なデータや挙動を模倣するための擬似オブジェクトです。これにより、特定の関数やモジュールが想定通りに動作するかを検証しやすくなり、エラーの原因を特定しやすくなります。

モックの型安全性

TypeScriptでモックを作成する際、型定義に基づいて正確にモックを設定することが重要です。型安全なモックを作成することで、テスト時に誤ったデータや挙動を防ぎ、保守性の高いテストコードを実現できます。

TypeScriptでの型安全なモックの作成方法

型キャストを使ったモックの作成

型キャストを使うと、モックを型安全に実装できます。たとえば、UserServiceというインターフェースがある場合、部分的にモックを作成する際に型キャストで型を調整します。

interface UserService {
  getUserById(id: string): Promise<User>;
}
const userMock: UserService = {
  getUserById: jest.fn().mockResolvedValue({ id: "123", name: "Alice" }),
};

ここでは、jest.fn().mockResolvedValueを使ってgetUserByIdの返り値を設定しています。TypeScriptの型推論に基づいて、型安全なモックが作成されます。

Partialを用いた一部モックの作成

すべてのプロパティやメソッドを含む必要がない場合、Partialを使って一部のモックを作成できます。これにより、不要なプロパティを省略しつつ、型整合性を保つことができます。

const partialUserMock: Partial<UserService> = {
  getUserById: jest.fn().mockResolvedValue({ id: "123", name: "Bob" }),
};
// 型キャストでUserService型として利用可能に
const userServiceMock = partialUserMock as UserService;

このように、Partialを使ってモックの型を柔軟に設定し、必要な部分のみモック化することで型安全なコードを維持できます。

jest.Mockedによる完全なモックの作成

Jestのjest.Mocked型を使うと、インターフェースやクラスのメソッドをすべてモック関数に置き換えた型安全なモックを生成できます。

const userServiceMock: jest.Mocked<UserService> = {
  getUserById: jest.fn().mockResolvedValue({ id: "456", name: "Charlie" }),
};

jest.Mockedを使うことで、関数やメソッドが自動的にモック化され、jest.fnを適用した型安全なモックオブジェクトを利用できます。

Jestでの型安全なモック作成の具体例

モジュールのモック化

Jestではjest.mockを使ってモジュール全体をモックすることができます。jest.mockを使うと、モジュールのすべての関数がモック関数に置き換えられるため、APIクライアントやデータベースアクセスなどの外部依存を簡単に模倣できます。

実装例

例えば、apiClientモジュールのfetchUser関数をモック化してテストする方法です。

// apiClient.ts
export const fetchUser = async (id: string): Promise<User> => {
  // 実際のAPI呼び出しロジック
};
// userService.ts
import { fetchUser } from "./apiClient";
export const getUser = async (id: string): Promise<User> => {
  return await fetchUser(id);
};

テストコードでfetchUserをモック化してテストします。

// userService.test.ts
import { getUser } from "./userService";
import * as apiClient from "./apiClient";
// モジュールのモック化
jest.mock("./apiClient");
describe("getUser", () => {
  it("should return a user by ID", async () => {
    const mockUser = { id: "789", name: "Dave" };
    (apiClient.fetchUser as jest.Mock).mockResolvedValue(mockUser);
    const user = await getUser("789");
    expect(user).toEqual(mockUser);
    expect(apiClient.fetchUser).toHaveBeenCalledWith("789");
  });
});

jest.mockを使用し、fetchUser関数を型安全にモック化しています。このようにすることで、fetchUserが外部に依存しない形でテストできます。

モック関数で引数と戻り値の型を指定する

Jestのjest.fnには、引数や戻り値の型を指定することができます。これにより、モック関数がテスト内でどのように動作するかをより詳細に制御できます。

interface Logger {
  log: (message: string) => void;
}
const loggerMock: jest.Mocked<Logger> = {
  log: jest.fn((message: string) => console.log(message)),
};
loggerMock.log("Test message");
expect(loggerMock.log).toHaveBeenCalledWith("Test message");

jest.fnの引数に型を指定することで、モック関数の引数や戻り値が型安全になり、テストの精度が向上します。

APIクライアントのテストでの型安全なモック

APIクライアントをテストする際、レスポンスデータやエラーハンドリングの型を定義することで、型安全にテストできます。たとえば、axiosを使ってデータ取得関数をモック化する際に型定義を適用します。

// apiClient.ts
import axios from "axios";
import { User } from "./types";
export const fetchUser = async (id: string): Promise<User> => {
  const response = await axios.get<User
>(`https://api.example.com/users/${id}`);
  return response.data;
};

テストコードでaxios.getをモックし、型安全なレスポンスを返すように設定します。

// apiClient.test.ts
import axios from "axios";
import { fetchUser } from "./apiClient";
import { User } from "./types";
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe("fetchUser", () => {
  it("should return a user object", async () => {
    const mockUser: User = { id: "1", name: "Alice", email: "alice@example.com" };
    mockedAxios.get.mockResolvedValue({ data: mockUser });
    const user = await fetchUser("1");
    expect(user).toEqual(mockUser);
    expect(mockedAxios.get).toHaveBeenCalledWith("https://api.example.com/users/1");
  });
});

ここではjest.Mocked<typeof axios>axiosの型をモック化し、型安全にaxios.getのレスポンスを設定しています。これにより、APIクライアントが外部の影響を受けずにテスト可能です。

型安全なモック作成のベストプラクティス

  1. インターフェースや型エイリアスを活用する
    モックを作成する際、インターフェースや型エイリアスを利用することで、モックが本来の型と一致しているかを保証しやすくなります。
  2. Partialや型キャストで柔軟に対応
    不要なプロパティを省略するためにPartialや型キャストを活用し、必要な部分だけをモックすることでテストが簡潔になります。
  3. jest.Mockedで型安全にモジュールをモック化
    Jestのjest.Mockedを使ってモジュール全体をモック化することで、関数やメソッドの型安全性を維持しつつ、外部依存を排除できます。
  4. 実行時の型検証ライブラリ(Zodやio-tsなど)の併用
    TypeScriptのコンパイル時の型チェックに加え、Zodなどの型検証ライブラリで実行時にも型安全性を保つと、さらに信頼性が向上します。

まとめ

TypeScriptでのユニットテストにおけるモックの型安全性は、コードの信頼性と保守性を向上させる重要な要素です。インターフェースや型キャスト、jest.Mockedを活用して型安全なモックを作成し、エラーの少ないテストコードを実現しましょう。JestとTypeScriptの機能を活かして、テストの精度と効率を高める型安全なユニットテストを構築してください。

Recommend