背景
Node.jsで動作するサーバーアプリのユニットテストコードをJestで作成するため、Jestの使用方法について調査した。
記事の目的
Jestで関数やクラス全体、クラスのメソッドの一部をMockする
Jest
ここでは、Jestについて記載する。
Jestとは
Jestは、Facebookが開発を進めている、オープンソースのJavaScriptのテストフレームワークである。TypeScriptで記述したものでも利用できる。テストフレームワークであるため、テストを書くために必要な一通りの機能(メソッドのMock機能、コードカバレッジレポート機能等)が提供されている。
Mock
Mockとは
ある関数やクラスのユニットテストを実行する際、それらが依存している外部モジュールの挙動によって実行結果が変化することは不適切である。そこで、外部モジュールを、一定の実行結果がモジュールに差し替える。この差し替えたモジュールをMockと言う。
関数のMock
/src/function.ts
/**
* 入力した数値を文字列に変換する
* @param input 数値
* @returns output 文字列
**/
export async function SampleFanc(input: number): Promise<string> {
return String<input>;
}
/**
* 入力した数値に1を足し、文字列に変換する
* @param input 数値
* @returns output 文字列
**/
export async function SampleFanc2(input: number): Promise<string> {
const out = SampleFanc(input+1);
return out;
}
上記の関数sampleFancをMockし、sampleFanc2をテストする場合、下記のようになる。
/test/function.test.ts
import * as sampleFanc from "../src/function.ts";
describe("SampleFancのモック化テスト", () => {
it("モック化できているか", () => {
// spyOnすることによって、該当関数(SampleFanc)の型がspyInplementationに変化する
// mockReturnValueOnce()によって1度だけ設定値を返す関数にMockできる
// jest.spyOnだけでは、実際の関数(Mockされていない関数)が実行されるので注意
const sampleSpy = jest.spyOn(sampleFanc , "SampleFanc").mockReturnValueOnce("1");
// SampleFanc2の実行、返り値が"2"でなければNG
expect(sampleFanc.sampleFanc2(1)).toBe("2");
// SampleSpyが1回以上呼ばれたかを確認、呼ばれていない場合NG
expect(SampleSpy).toHaveBeenCalled();
});
});
}
クラス全体のMock
/src/class.ts
/**
* 入力した数値を文字列に変換する
* @param input 数値
* @returns output 文字列
**/
export class SampleClass {
private data: string;
/**
* 空文字列をセット
*/
constructor() {
this.data= "";
}
/**
* 入力された文字列をキャッシュし、前の文字列を返す
* @param input キャッシュする文字列
* @returns output キャッシュされていた文字列
*/
public Func1(input: string): string {
const output = this.data;
this.data = input;
return output;
}
/**
* 入力された数値を文字列に変換してキャッシュし、前の文字列を返す
* @param input キャッシュする数値
* @returns output キャッシュされていた文字列
*/
public Func2(input: number): string {
const output = this.Func1(String(input));
return output;
}
}
上記のクラスsampleClassをMockする場合、下記のようになる。
/test/function.test.ts
import * as sampleClass from "../src/class.ts";
// jest.mock()によってクラス全体をモック化できます
jest.mock("../src/class.ts"); // パスを指定
const SampleClassMock = sampleClass.SampleClass as jest.Mock; // TypeScriptでは型変換する必要がある
describe("SampleClass のテスト", () => {
it("クラス全体がモックになっているか確認", () => {
// mockImplementationOnceで実装したいクラスを設定する
SampleClassMock.mockImplementationOnce(() => {
return {
data: "1",
Func1: (): string => {
return "2";
},
Func2: (): string => {
return "3";
},
};
});
const sample = new sampleClass.SampleClass();
// SampleClassMockが1度以上呼び出されたか確認
expect(SampleClassMock).toHaveBeenCalled();
// プライベート変数"name"が"1"であるか確認
expect(sample["data"]).toBe("1");
// メソッドFunc1が"2"を返すか確認
expect(sample.Func1("1")).toBe("2");
// メソッドFunc2が"3"を返すか確認
expect(sample.Func2(1)).toBe("3");
});
});
クラスの一部をMock
/src/class.ts
/**
* 入力した数値を文字列に変換する
* @param input 数値
* @returns output 文字列
**/
export class SampleClass {
private data: string;
/**
* 空文字列をセット
*/
constructor() {
this.data= "";
}
/**
* 入力された文字列をキャッシュし、前の文字列を返す
* @param input キャッシュする文字列
* @returns output キャッシュされていた文字列
*/
public Func1(input: string): string {
const output = this.data;
this.data = input;
return output;
}
/**
* 入力された数値を文字列に変換してキャッシュし、前の文字列を返す
* @param input キャッシュする数値
* @returns output キャッシュされていた文字列
*/
public Func2(input: number): string {
const output = this.Func1(String(input));
return output;
}
}
上記のクラスsampleClassのFunc1のみをMockする場合、下記のようになる。
/test/function.test.ts
import * as sampleClass from "../src/class";
describe("SampleClass のテスト", () => {
it("Func1がモックになっているか確認", () => {
// SampleClass.prototypeのfunc1関数をspyOnすることで、func1関数のモック化ができる
const Func1Spy = jest.spyOn(sampleClass.SampleClass.prototype, "Func1").mockReturnValue("2");
const sample = new sampleClass.SampleClass();
// Func2の実行結果が"2"であるか確認
expect(sample.Func2(1)).toBe("2");
// Func1Spy が1度以上呼び出されたか確認
expect(Func1Spy).toHaveBeenCalled();
});
});
まとめ
- Jestで関数やクラス全体、クラスのメソッドの一部をMockする方法について調査、記載した
参考文献
変更履歴
- 2020/07/01: 新規作成