モック機能を使うと、
モジュール外機能を簡略化
関数は何と共に何回呼ばれたか(
spy
)
ができます。
モジュール外機能を簡略化
例えばあるクラスA
には、内部で API を通してデータを保存・取得するメソッドを持っていたとします。API には多分何かしらの制約を持ってるのではと思います。例えばこんな感じです。
あるフィールドはユニークな値しか保存できない
レスポンスにランダムな
id
を返す取得にはランダムな
id
を使う
このような時に実際に API 叩いてしまうと、決められた順番に実行しなければパスできない実質1つの巨大なテストケースになってしまいます。後処理が必要なものだったりするともっと大変。
API の周りの機能は完全にモジュール外の要素です。なのに実際に API まで叩かないといけないのか。
特にユニットテストレベルではその辺までしっかりする必要性は薄いです。なので、この辺(+α)を以下のような決めた値を返すだけのスタブに変えてしまいます。
保存できる
レスポンスに
id
をbyMVc
として返すid
が何でも取得できるまとめて取得した場合は2件データが入っている
Jest にはこのような既存のモジュールをモック化するいくつかの機能を持ってます。
jest.mock
もしあるモジュールがエクスポートしてる機能をまとめてモック化したいならjest.mock(path)
を使います。これは関数はもちろんオブジェクトが持ってるメソッドなどをすべてundefined
を返すモック(spy
)に書き換えます。
例えば開発しているモジュールmy-client.js
が以下のような実装だとします。
const fetch = require('cross-fetch');
module.exports = class MyClient {
constructor(token) {
this.token = token;
}
/**
* データ取得
* @returns {Promise
また、テストファイルでは以下のようにこのモジュールを使ってるとします。
const MyClient = require("./my-client");
test("get", async () => {
const client = new MyClient("...");
expect(client.get()).resolves.toMatchObject([
{ id: "a" },
{ id: "b" }
]);
});
my-client
はcross-fetch
モジュールに依存しており、今の所get
テストでは実際にcross-fetch
を通してリクエストを飛ばしてしまいます。そしてレスポンスデータが2件の時だけパスできます。もしこの後保存 API を飛ばすと API 仕様によっては、このテストは失敗するようになるかもしれません。
ここでcross-fetch
をモック化して結果的に必ず[{id: 'a'}, {id: 'b'}]
を返す関数にしてしまいます。それには以下のような行を挟みます。
const fetch = require('cross-fetch');
jest.mock('cross-fetch');
fetch.mockResolvedValueOnce({
json: () => [{ id: "a" }, { id: "b" }]
});
mockResolvedValueOnce(value)
は1度だけPromise.resolve(value)
を返すモック関数化できます。my-client
の中のget
関数は、単に取得したデータを返すだけなので、これで常にget
テストが通るようになります。
上記のテストではテストファイルで直接モックを作りました。しかし中にはいくつものモック関数をテストファイルの中で作る必要があったり、何度も同じことを書く必要があったりで使いまわしたい場面がでてくるかもしれません。
そのような場合にはマニュアルモックが使えます。
マニュアルモック
マニュアルモックは、テストファイルと同じ階層に__mocks__/cross-fetch.js
のように__mocks__/
の中にモジュール名ファイルを入れることで、jest.mock('cross-fetch')
時にそのファイルに書かれた内容でモック化してくれる機能です。
例えば先程のcross-fetch
と同様のマニュアルモックを作ると以下のようになります。
const fetch = jest.genMockFromModule("cross-fetch");
fetch.mockResolvedValueOnce({
json: () => [{ id: "a" }, { id: "b" }]
});
module.exports = fetch;
これからはjest.mock('cross-fetch')
とするとfetch
した時にPromise.resolve([{ id: "a" }, { id: "b" }])
が返されるようになりました。
モック関数
先程出てきたmockResolvedValueOnce(value)
は「1度だけPromise.resolve(value)
を返す」という関数にするものでしたが、他にも色々あります。
mockResolvedValue(value)
は1度じゃなく常にPromise.resolve(value)
を返すようにします。同じようにmockRejectedValue
,mockRejectedValueOnce
やmockReturnValue
とmockReturnValueOnce
があり、それぞれPromise.reject(value)
とvalue
版です。
また完全に自分で実装するmockImplementation(fn)
とmockImplementationOnce(fn)
もあります。引数のfn
は単なる関数で、モック関数が呼ばれた時に単にそれが呼ばれ結果が戻り値で返ります。
モックは受け取った引数や戻り値を覚えています。それらはそれぞれmock.calls
とmock.results
を通して取得できます。例えばmock.calls[0][1]
で1回目実行時の第2引数、mock.results[0]
で1回目実行時の戻り値のような感じです。
上記のmock.calls
やmock.results
の履歴を消したい時はmockReset
を実行します。また、mockClear
を呼ぶとモックをjest.fn()
直後の状態に戻せます。