jest テストコード

今日から始める!:Jestでテスト(モックテスト)

Jest アイキャッチ画像

 

nao
Jestを使ってテストコードを書き始めたんだけど、mockやspyがわからない。。

 

今回はこんな疑問を解消します。

 

こんな方におすすめ

  • jestを使ったmockテストの書き方を知りたい人
  • spyを使用して関数のmock化のやり方を知りたい方

 

jestでテストコードを書いているとmockテスト、spyOnで関数のmock化に関する知識は必要になってきます。初心者の方向けに丁寧に解説をしていくのでご安心ください。

環境構築、基本的なテストの書き方に関しては以下の記事で解説しているので、まだの方はまずはここからやりましょう。

 

こちらもCHECK

Jestアイキャッチ画像
今日から始める!:Jestでテスト(基本的なテスト)

続きを見る

 

nao
さっそくやっていきましょう

 

mock、spyの概要

 

コードを書いていく前にmock、spyの概要について説明をします。

 

mock

mockは実際のオブジェクト、関数、モジュールの代わりに使用される偽のオブジェクトや関数です。mockを使用する主な理由は以下のとおりです。

  1. 依存性の分離: mockを使用することで、テスト対象となるコードを他の依存性から独立させることができます。
  2. 振る舞いの制御: mockは特定の振る舞い(例えば、関数が呼び出されたときの戻り値など)をエミュレート(模倣)することができます。
  3. 呼び出しの確認: mockはそれ自体が何回呼び出されたのか、どのような引数で呼び出されたのかなどを確認することができます。

Jestで最も一般的に使用されるmock関連の関数は jest.mock()jest.fn() です。

 

 html
// jest.mockを使って全体のモジュールをモック化
jest.mock('axios');
// jest.fn()を使って個々の関数をモック化
const myMockFn = jest.fn();

 

 

Spy

spyはオブジェクトや関数がどのように呼び出されたのかを“監視”します。spyは、関数が何回呼び出されたか、どのような引数で呼び出されたか、戻り値は何かなどを報告します。

Jestでスパイを使用する主な方法は jest.spyOn() です。この関数は、オブジェクトとそのプロパティ名を引数に取り、そのプロパティ(通常は関数)にスパイをセットします。

 

 html
const math = {
add: (a, b) => a + b,
};
// math.addメソッドにスパイをセット
const spy = jest.spyOn(math, 'add');
math.add(1, 2); // スパイを通して実際の関数が呼び出される
expect(spy).toHaveBeenCalled(); // スパイを使って関数が呼び出されたかどうかを確認

 

jest.spyOn() によって作成されたspyは、実際の関数の振る舞いをそのままにしつつ、その呼び出しをトラックできます。また、.mockImplementation() や .mockReturnValue() などを使用して、spyした関数の振る舞いを変更することも可能です。

 

Jestによるmockの作成

 

ここから実際にコードを書いていきましょう!

mock.test.tsを作成し、中身を以下のようにしてください。

 

 mock.test.ts
it("初mock", () => {
const mockFunc = jest.fn(() => "Hello mock");
expect(mockFunc()).toBe("Hello mock");
});
it("mockImp", () => {
const mockFunc = jest.fn();
mockFunc.mockImplementation(() => "Hello mock2");
expect(mockFunc()).toBe("Hello mock2");
});

 

最初のテストケースでは、jest.fn()を使ってmock関数mockFuncを作成しています。このmock関数は、呼び出されると"Hello mock"という文字列を返します。jest.fn(() => "Hello mock")とすることで、mock関数の戻り値が"Hello mock"になるように設定しています。その後、expect(mockFunc()).toBe("Hello mock");でこのmock関数を呼び出し、返される値が期待通りであるかを確認しています。

 

次のテストケースでも、mock関数を作成していますが、こちらはモック関数の実装を後から設定しています。最初に空のmock関数mockFuncを作成し(jest.fn())、その後mockImplementationメソッドを使ってこのmock関数が呼び出されたときの動作を定義しています。

具体的には、mockFunc.mockImplementation(() => "Hello mock2");とすることで、このmock関数が呼び出されたときに"Hello mock2"を返すようにしています。

そして、expect(mockFunc()).toBe("Hello mock2");でこのmock関数を呼び出し、返される値が期待通りであるかを確認しています。

 

mock関数の戻り値の設定

 

次にmock関数の戻り値の設定をみていきましょう。

mock_return.test.tsを作成して中身を以下のようにしてください。

 

 mock_return.test.ts
it("mock関数に戻り値を設定", () => {
const mockFunc = jest.fn();
mockFunc.mockReturnValue("Mock return value");
expect(mockFunc()).toBe("Mock return value");
});
it("mock関数に一度だけ戻り値を設定", () => {
const mockFunc = jest.fn();
mockFunc.mockReturnValueOnce("Mock return value");
expect(mockFunc()).toBe("Mock return value");
expect(mockFunc()).toBe(undefined);
});
it("mock関数に非同期戻り値を設定", async () => {
const mockFunc = jest.fn();
mockFunc.mockResolvedValue("Resolved value");
const result = await mockFunc();
expect(result).toBe("Resolved value");
});

 

最初のテストでは、mockReturnValue() メソッドを使用してモック関数mockFuncの戻り値を "Mock return value" に設定しています。その後、expect(mockFunc()).toBe("Mock return value"); でこのモック関数を呼び出し、返される値が期待通りであるかを確認しています。

次のテストではmockReturnValueOnce() メソッドを使用して、モック関数mockFuncが一度だけ "Mock return value" を返すように設定しています。その後、同じモック関数を2回呼び出しています。1回目の呼び出しで "Mock return value" が返され、2回目の呼び出しで undefined が返されることを確認しています。

最後のテストではmockResolvedValue() メソッドを使用して、モック関数mockFuncがPromiseを返し、そのPromiseが "Resolved value" で解決されるように設定しています。その後、await mockFunc(); でこの非同期のモック関数を呼び出し、返されるPromiseが解決された値が期待通りであるかを確認しています。

 

mock関数の呼び出しの検証

 

次はmock関数が呼び出されたかの検証をテストします。

mock_called.test.tsを作成して中身を以下のようにしてください。

 

 mock_called.test.ts
it("mock関数が呼び出される", () => {
const mockFunc = jest.fn();
mockFunc();
expect(mockFunc).toHaveBeenCalled();
});
it("mock関数が2回呼び出される", () => {
const mockFunc = jest.fn();
mockFunc();
mockFunc();
expect(mockFunc).toHaveBeenCalledTimes(2);
});
it("mock関数に引数testが渡される", () => {
const mockFunc = jest.fn();
mockFunc("test");
expect(mockFunc).toHaveBeenCalledWith("test");
});

 

最初のテストでは、まず空のモック関数mockFuncを作成しています。その後、このモック関数を呼び出しています(mockFunc();)。最後にexpect(mockFunc).toHaveBeenCalled();でモック関数が一度以上呼び出されたかどうかを検証しています。

次のテストでは、空のモック関数mockFuncを作成し、それを2回呼び出しています(mockFunc(); mockFunc();)。最後にexpect(mockFunc).toHaveBeenCalledTimes(2);で、モック関数がちょうど2回呼び出されたかどうかを検証しています。

最後のテストでは空のモック関数mockFuncを作成し、それに"test"という引数で呼び出しています(mockFunc("test");)。最後にexpect(mockFunc).toHaveBeenCalledWith("test");で、モック関数が"test"という引数で呼び出されたかどうかを検証しています。

 

spyonを使用した関数のmock

 

ここからはspyonを使用して関数をmockしていきましょう。

mock_spy.test.ts、mock_spy.tsを作成してください。

 

 mock_spy.ts
export class Calculator {
sum(a: number, b: number): number {
return a + b;
}
}

 

まず、Calculator クラスにはsumというメソッドが定義されており、そのメソッドは2つの数値を引数として受け取り、その和を返します。

 

 mock.test.ts
import { Calculator } from "./mock_spy";
it("sum関数が呼び出される", () => {
const calculator = new Calculator();
const sumSpy = jest.spyOn(calculator, "sum");
const result = calculator.sum(1, 2);
expect(result).toBe(3);
expect(sumSpy).toHaveBeenCalledTimes(1);
expect(sumSpy).toHaveBeenCalledWith(1, 2);
sumSpy.mockRestore();
});

 

jest.spyOn()を使用してcalculatorオブジェクトのsumメソッドをスパイ(監視)しています。これにより、このメソッドがどのように呼び出されたかを後で検証できます。

calculator.sum(1, 2);を呼び出して、その結果が3であることを確認しています。sumSpyを使って、sumメソッドがちょうど1回呼び出されたかを確認して、さらに、sumメソッドが引数1と2で呼び出されたかも検証しています。
最後に、スパイをクリーンアップしています。これはテストが他のテストに影響を与えないようにするために良い習慣です。

 

モジュール全体のmock化

 

最後にモジュール全体をmock化しましょう。

mock.module.ts、mock.module.test.tsを作成してください。

 

 mock.module.ts
import fs from "fs";
export function readFile(path: string) {
const data = fs.readFileSync(path, {
encoding: "utf-8",
});
return data;
}

 

この関数は、指定されたpathでファイルを読み、そのデータを返します。fs.readFileSyncを使用してファイルを同期的に読み込んでいます。

 

 mock.module.test.ts
import fs from "fs";
import { readFile } from "./mock_module";
jest.mock("fs");
const mockFs = jest.mocked(fs);
mockFs.readFileSync.mockReturnValue("dummy data");
it("readFileがデータを返却", () => {
const result = readFile("path/dummy");
expect(result).toBe("dummy data");
expect(fs.readFileSync).toHaveBeenCalledTimes(1);
});

fsモジュールをmockしています。jest.mock("fs")を使用することで、fsモジュールの関数をJestのモック関数に置き換えています。

mockしたfs.readFileSyncを使ってreadFile("path/dummy")を呼び出します。mockしたfs.readFileSyncが"dummy data"を返すように設定しているので、readFileも"dummy data"を返すはずです。expect(result).toBe("dummy data");でそれを検証しています。
最後にfs.readFileSyncがちょうど1回呼び出されたかを検証しています。
これにより、readFile関数が期待通りに動作するか、また、fs.readFileSyncが正しく呼び出されるかがテストされています。

 

まとめ

 

前回に引き続き基本的なテストを通してテストコードの書き方を学びました。テストコードは開発において必須なので今回学んだことをインプットアウトプットしていきましょう!

ここまでみてくださりありがとうございました!

 

-jest, テストコード