2020/07/01

Jestで関数、クラスをMockする方法


背景


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する方法について調査、記載した

参考文献




変更履歴


  1. 2020/07/01: 新規作成

0 件のコメント:

コメントを投稿

MQTTの導入

背景 IoTデバイスの接続環境構築のため、MQTT(mosquitto)の導入を行った。 記事の目的 MQTT(mosquitto)をUbuntuに導入する mosquitto ここではmosquittoについて記載する。 MQTT MQTT(Message Qu...