🐈

Typescriptで外部へのhttpアクセスを含むビジネスロジックをjestでテストするすごく簡単な方法

2023/01/10に公開

前置き

Typescriptでビジネスロジックを本当にちゃんと作るってテストコードも書く場合、ちゃんとClean Architectureみたいにレイヤーを切って外部へのアクセスはRepository層を作ったり、ビジネスロジックはUseCase層を作ってDIみたいなことをすると思います。それでMockitoとか使ってmockで置き換えるみたいな。

ただ、そういうレイヤー分けやDIは、プロジェクトの最初からCleanArchitecture等のレイヤー分けをわかってる人がやるならうまくいくんですが、そういう人がいないとか途中から参画したみたいなプロジェクトだと大変だと思います。

HTTPアクセスくらいだったらMSWとか使ってもいいですけど、導入・メンテナンスが大変です。

とりあえずビジネスロジックをテストしたい!その前にClean ArchitectureとかDIとか考えたくない!自分はわかるけどチームメンバーに教える時間もない!って人向けに究極までに手抜きしたサンプルをご紹介しようと思います

手順

  1. ビジネスロジック用のディレクトリを用意。名前はなんでもいいので今回はlogicsにする
  2. User関連だったら、先程のlogicsディレクトリ内にuserディレクトリ作成。logics/user/となる
  3. Userを作成する処理を作りたいとしたら、createUser.tsを作成。
  4. 以下のように書く。コツはテストしづらい処理は、別にオブジェクト作って、その中に関数としてまとめること。
createUser.ts
export interface User {
  name: string;
  age: number;
}

// httpアクセス等のテストが難しい副作用系をまとめる。お願いだからテストコード以外ではimportしないで。。。
export const _sideEffects = {
  async create(user: User) {
    await axios.post(...);
  },
} as const;

// わかりやすいようにファイル名と同名の関数を用意
export default async function createUser(user: User) {
  // ここでなんらかの処理を行う
  console.log("business logic");

  // 副作用だけ別オブジェクトに格納するしてアクセスする。こうするとjest.spyOnしやすい。
  await _sideEffects.create(user);
}
  1. createUser.test.tsを作成してテストコードを書く。テストが難しい副作用系をまとめたことでjest.spyOnで、処理をモック化しやすくなってる。
createUser.test.ts
import createUser, { User, _sideEffects } from "./createUser";

beforeEach(() => {
  jest.clearAllMocks();
});

test("normal", async () => {
  const user: User = {
    name: "test",
    age: 20,
  };
  // 普通に関数を宣言すると、spyOnでモック化しても、スコープの関係で実行時にモック前の本物の関数を見に行ってしまうので、一旦別オブジェクトに入れる必要がある。ついでにテストしづらい処理の閲覧性も高まる。
  const spy = jest.spyOn(_sideEffects, "create").mockResolvedValue(undefined);
  const result = await createUser(user);

  expect(result).toBeUndefined();
  expect(spy).toHaveBeenCalled();
});

まとめ

簡単に言えば、一つのビジネスロジックに対して、1ファイル用意して、テストが難しい処理の箇所は、別オブジェクトに入れたらテスト書きやすくなるよって話。

めちゃくちゃ手を抜けるようにしたため、色々穴とかツッコミどころはあると思いますが、処理分割するだけでプラグインとか導入する必要もなくて簡単なので、とりあえず始めて見る分にはいいんじゃないかなと思います。

Discussion