背景
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: 新規作成