「イベントが呼び出されたかどうか」ということをモック関数を使って確かめる方法に関するメモ。
Jestそのものの導入、操作方法は過去記事参照。
公式ドキュメントは以下。
テスト用のクラスを用意する
以下のようなクラスを用意した。
import { EventEmitter } from "events";
const emitter = new EventEmitter();
export class TestClass extends EventEmitter{
constructor(){
super();
}
testPing = () => {
this.emit("testEvent", {"type": "test"});
}
}
testPing
関数を動かすと、testEvent
イベントが帰ってくる。これが正常に動くかどうか確かめたい。
求めるのは以下のようなテストとなる。
-
testEvent
が呼び出された際に引数内にtype: "test"
が含まれていればテストが通る -
testEvent
が呼び出された際に引数内にtype: "test"
が含まれていなければテストが通らない -
testEvent
が呼び出されなければテストが通らない
ダメな例
以下のようなテストコードを作ってみる。
import {TestClass} from "../src/eventTest";
it("should get event with correct type", () => {
const testClass = new TestClass();
testClass.on("testEvent", (msg) => {
expect(msg).toEqual(
expect.objectContaining({
type: "test",
})
);
});
testClass.testPing();
});
TestClass
のインスタンスを作成し、testPing
を動かす。
testEvent
の発火時にイベントリスナーを動かし、受け取ったメッセージを確認する。ここにtype: "test"
というオブジェクトが含まれていれば通る。
これで一見よさそうだが、このまま動かすと以下のようなテストになる。
-
testEvent
が呼び出された際に引数内にtype: "test"
が含まれていればテストが通る -
testEvent
が呼び出された際に引数内にtype: "test"
が含まれていなければテストが通らない -
testEvent
が呼び出されなければテストが通る- 本来は通ってはいけない
例えばclassの方を少し書き換えてみる。
testPing = () => {
this.emit("testEvent_", {"type": "test"});
}
testEvent
ではなくtestEvent_
が発火するようにした。この場合先ほどのテストは通らないでほしいが...
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
通ってしまう。 これはイベントの呼び出しの有無を確かめてるわけではないため。
これを正しくテストするために、モック関数というものを使用する。
モック関数って何?
「関数がどのように呼び出されたか」ということを捉える関数。これを用いることで今まで何回その関数が呼び出されたか、どのような引数を持って呼び出されたかを取得することができる。
以下のようにして作成できる。
const testEventListener = jest.fn();
ここから、以下のようなexpect文で呼び出しに関する情報を調べることができる。
// 呼び出されたか否か
expect(testEventListener).toHaveBeenCalled();
// 指定の引数で呼び出されたことがあるか
expect(testEventListener).toHaveBeenCalledWith(arg1, arg2);
// 前回の呼び出しで指定の引数だったか
expect(testEventListener).toHaveBeenLastCalledWith(arg1, arg2);
正しい実装例
というわけで、以下のように実装できる。
import { TestClass } from "../src/eventTest";
it("should get event with correct type", () => {
const testClass = new TestClass();
const testEventListener = jest.fn();
testClass.on("testEvent", (msg) => {
testEventListener(msg);
});
testClass.testPing();
expect(testEventListener).toHaveBeenCalledWith(
expect.objectContaining({
type: "test",
})
)
});
これで正常に動くようになる。
イベント発火時にはモック関数を動かすだけのリスナーを登録し、
testClass.on("testEvent", (msg) => {
testEventListener(msg);
});
その後そのモック関数が呼ばれているかを確認した。
expect(testEventListener).toHaveBeenCalledWith(
expect.objectContaining({
type: "test",
})
)
これで
-
testEvent
が呼び出された際に引数内にtype: "test"
が含まれていればテストが通る -
testEvent
が呼び出された際に引数内にtype: "test"
が含まれていなければテストが通らない -
testEvent
が呼び出されなければテストが通らない
という条件を満たすことができた。