Motivation
テストするときには実際にAWSにアクセスさせずに Unit testing や Integration testing をしたい場合があります。この場合に使うのが Mocking ですが、 jest
での Mocking のやり方を整理します。
package ごと Mocking する方法は二つある
package ごと Mocking する方法は以下の二つがあります。
-
__mocks__
というディレクトリを作ってそこに Mocking した処理を書く - テストファイルごとに Mocking した処理を書く
package を包括的に mock する場合は1, packageの中のメソッドを spying する場合は2で実装します。
__mocks__
を使う場合
こちらを使う場合は jest.config.js
で automock
を true
にセットしてあげると、それぞれのファイルで自動的に __mocks__
にあるモジュールを読み込んでテストを実行してくれるので、それがメリットかもしれません。
1. __mocks__
という名のディレクトリを作成する
Mockしたい対象同じ階層に __mocks__
というディレクトリを作成します。小文字でないといけませんし、__mock__
という単数形でも動作しませんのでご注意ください。
この記事では package を mock するという記事ですので、node_modules
と同じ階層にディレクトリを作成することになります。
階層イメージはこのとおりです(出典は Manual Mocks )。この例では node_modules
にある fs
という package の他に models
というディレクトリにある user.js
を mock しようとしている例です。
.
├── config
├── __mocks__
│ └── fs.js
├── models
│ ├── __mocks__
│ │ └── user.js
│ └── user.js
├── node_modules
└── views
2. __mocks__
というディレクトリに mock ファイルを作成する
mock する対象をファイル名にしてファイルを作成します。この例では cloudinary の Node.js 向け package の mock を例にします。cloudinary.ts
というファイルを作成しました。
import { createPhotos } from '@Test/createPhotos';
const cloudinary = jest.genMockFromModule('cloudinary');
const { firstPhotoInClourinaryMock } = createPhotos();
const v2 = {
config: ({ }: { cloud_name: string; api_key: string; api_secret: string }) => {},
uploader: {
upload: (url: string, _: any, callback) => {
if (url === 'error') {
callback(new Error('Error'), null);
} else {
callback(null, firstPhotoInClourinaryMock);
}
},
destroy: (id: string, callback) => {
if (id === 'error') {
callback(new Error('Error'), null);
} else {
callback(null, { result: 'success' });
}
},
},
};
// @ts-ignore
cloudinary.v2 = v2;
module.exports = cloudinary;
jest.genMockFromModule
このメソッドによって package を mock することができます。genMockFromModule
を使わずに完全にカスタムでも mock は作れますが、使わない理由がここではないので使いました。このメソッドによって、元々 cloudinary
のモジュールにあった処理は上書きされます。
cloudinary の処理を mock する
cloudinary
の使い方を見ると以下がわかります。
-
cloudinary
は object でv2
という object がある - その object の中に
config
というメソッドがある - 1の object の中に
uploader
というobjectがあり、それにupload
とdestroy
というメソッドがある
cloudinary.v2.config()
cloudinary.v2.uploader.upload()
cloudinary.v2.uploader.destroy()
そこで、mock では v2
という objectを作り、それに上記でわかった1, 2, 3を実装しました。それぞれのメソッドの使い方を見て、実際のモジュールの動きをさせつつ、テストデータを伴ってコールバックを実行する必要がある場合にはコールバックを実行させるようにしています。
なお補足ですが、
import { createPhotos } from '@Test/createPhotos';
const { firstPhotoInClourinaryMock } = createPhotos();
これは自前で作っているテストデータを作成する関数です。
mock を発動する
あとはテストファイルで以下を冒頭に書いてあげてください。
jest.mock('cloudinary');
これで cloudinary
の mock したモジュールが読み込まれます。または jest.config.js
で automock
を true
にセットしてあげると、それぞれのファイルで自動的に __mocks__
にあるモジュールを読み込んでテストを実行してくれます。
jest の公式マニュアルにある Manual Mocks にある内容に準じます。
テストファイルの中で mock を作成する
テストファイルの中で jest.mock()
を使って mock することもできます。package のあるメソッドを spying したい場合はこちらで実装します。
テストファイルの冒頭で mock を作成する
この例では aws-sdk
の DynamoDB
クラスを mock してみます。DynamoDB.DocumentClient
のインスタンスを生成して DynamoDB
にアクセスする処理を持っているとします。
1. package の使い方を見ながら mock を作る
aws-sdk
の使い方を見ると aws-sdk
自体が object であり、DynamoDB
はクラスであり、またその中にある DocumentClient
もまたクラスであることがわかります。JavaScriptではクラスは function そのものなので、ネストした object を mock として作りこんであげます。
また mock する前に mockPut
という変数を作って jest.fn()
のアウトプットを入れているのは、 spying するためです。これによって何回この mock した関数が呼ばれたか(toHaveBeenCalledTimes
)とか、意図した引数と共に関数が呼ばれたか(toHaveBeenCalledWith
)どうかをテストできます。
また mock した put メソッドのアウトプットは aws-sdk
のドキュメントや node_modules
の中を見てどういうアウトプットを返すのかを見た上で実装しています。
import { DynamoDB } from 'aws-sdk';
const mockPut = jest.fn();
jest.mock('aws-sdk', () => {
return {
DynamoDB: {
DocumentClient: jest.fn(() => {
return {
put: mockPut.mockImplementation(() => {
return {
promise: async (): Promise<DynamoDB.DocumentClient.PutItemOutput> => ({
Attributes: undefined,
ConsumedCapacity: undefined,
ItemCollectionMetrics: undefined,
}),
};
}),
};
}),
},
};
});
2. テスト実行前に clear, テスト実行後に restore
必要に応じてテスト実行前に mock の状態をクリアしたり、テストが終わった後に mock ではない状態に戻してあげましょう。各テストケース実行前に状態をクリアしないと、関数を何回呼び出しかどうかの記録が積み上がってしまいます(一回しか実行していないはずなのに2回3回となる)。
beforeEach(() => {
mockPut.mockClear();
});
まとめ
再掲します。
package ごと Mocking する方法は以下の二つがあります。
-
__mocks__
というディレクトリを作ってそこに Mocking した処理を書く - テストファイルごとに Mocking した処理を書く
package を包括的に mock する場合は1, packageの中のメソッドを spying する場合は2で実装します。
ただしテストを書いているとやはり package のメソッドを spying したいので2で各パターンが僕は圧倒的に多いです。jest の spyOn
というのもあるので、 spying に関してまた別の記事で書きたいと思います。