JavaScriptで単体テストする際は、Jestを使うのがデファクトになってきています。 単体テストでは、関連するモジュールをモックにしてテストすることが多いですね。 ここでは、Jestのモックの機能と使い方をユースケース別に説明します。
例えば、自分の関数内でDate.now()
を使って時間を取得していると、テストを実行するたびに値が異なるため、テストがうまくいかないことがあります。
そのような場合、jest.spyOn
を使います。spyOn
を使うことである特定の時間を返すことができるようになります。
class Test {
func() {
return Date.now();
}
}
describe("Date#now", () => {
it("spyOnを使うと好きな時間に固定することができる", () => {
const spy = jest.spyOn(Date, "now");
spy.mockReturnValue(1577804400000); // 2020/01/01
expect(new Test().func()).toBe(1577804400000);
spy.mockRestore();
});
});
spy.mockRestore()
を忘れてしまうと、他のすべてのテストでDate.now()
がモックされた状態のままになってしまいます。
わざわざ書くのめんどいよ、という人は、JestのオプションにrestoreMocksという項目があるので、こちらの利用を検討してもよいでしょう。
インスタンスAの中でインスタンスBのメソッドを呼び出して、その結果を使って何かする、みたいな処理ってよくあるじゃないですか。
class ClassA {
constructor(readonly b: ClassB) {}
func() {
return this.b.someFunc().toUpperCase();
}
}
class ClassB {
someFunc() {
return "hello world.";
}
}
ClassAのテストとしては、ClassBの関数を呼んだ結果をtoUpperCase()
している、ということだけテストできればよいわけです。ClassBの実体を使っていると、ClassBの仕様が変わったときにClassAのテストも変えないといけなくなります。
そこで、ClassAのテストではClassBのモックを使います。
まずは、ただのオブジェクトにsomeFunc
という関数を持たせる方法でやってみます。
ただのオブジェクトをClassBのインスタンスだと偽っており、立派なモックの役割を果たしています。
describe("ClassA#func", () => {
it("ClassB#someFuncの結果をtoUpperCaseしていることをテストする", () => {
const bMock = { someFunc: () => "foo" };
const a = new ClassA(bMock);
expect(a.func()).toBe("FOO");
});
});
ところで、上記例では、a.func()
の内部実装で"foo".toUpperCase()
してても通っちゃうんですよね。ClassBのメソッドが呼ばれたかどうかは検証できていないわけです。呼ばれたことをあとで確認できるしくみはないでしょうか。
jest.fn()
を使います。
これはモック関数と呼ばれる関数を返します。モック関数は呼ばれてもなにもしませんが、どのような引数で何回呼ばれたかを記録しています。また、必要であれば、戻り値を指定したり、内部実装を書いたりもできます。
これを使ってよりよくしてみましょう。
code.spec.ts
describe("ClassA#func", () => {
it("ClassB#someFuncの結果をtoUpperCaseしていることをテストする", () => {
const bMock = { someFunc: jest.fn() };
bMock.someFunc.mockReturnValueOnce("foo");
const a = new ClassA(bMock);
expect(a.func()).toBe("FOO");
expect(b.someFunc).toBeCalledTimes(1);
});
});
いかがでしょうか。jest.fn()
が返す関数オブジェクトは特殊で、いくつかのメソッドが生えており、ここではmockReturnValueOnce
を使って、呼ばれたら一度だけ決まった値を返すように設定しています。
また、Jestが提供するexpect
で関数が1度だけ呼ばれたことを確認しています。
これなら、ClassBのメソッドが呼ばれていそうなことも確認できましたね。
jest-when
でより読みやすい書き方にするjest-when
というライブラリを追加してより読みやすい書き方にできます。
code.spec.ts