はじめに
結構前の話になりますが、AWSのBlogを読み漁っていると、以下のような記事を見つけました。
どうやらJS用のAWS-SDKのクライアント用のMockライブラリがあるらしい。。。
というわけで、さっそく試してみることにした。
AWS SDK v3 Client mock
詳細は、以下GitHubのリンクを参照ください。
使い方(例)
「S3のバケットからオブジェクトを取得する処理」を例にしてMockライブラリを試してみようと思う。
例(S3のバケットからオブジェクトを取得する処理)
// src/main.ts
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
export async function main() {
try {
const s3 = new S3Client({ region: 'ap-northeast-1' })
const obj = await s3.send(
new GetObjectCommand({
Bucket: 'dummyBucket',
Key: 'dummyKey',
}),
)
if (obj.$metadata.httpStatusCode && obj.$metadata.httpStatusCode >= 400) {
return 'NG'
}
return 'OK'
} catch (error) {
throw error
}
}
テストの例(S3のバケットからオブジェクトを取得する処理)
// test/main.spec.ts
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { mockClient } from 'aws-sdk-client-mock'
import fs from 'fs'
import path from 'path'
import { main } from '../src/main'
const s3Mock = mockClient(S3Client)
beforeEach(() => {
s3Mock.reset()
})
describe('S3のオブジェクトを取得できる', () => {
it('成功', async () => {
s3Mock
.on(GetObjectCommand, {
Bucket: 'dummyBucket',
Key: 'dummyKey',
})
.resolves({
$metadata: {
httpStatusCode: 200,
},
Body: fs.createReadStream(path.resolve(__dirname, './example.txt')),
})
const result = await main()
expect(result).toBe('OK')
})
it('失敗', async () => {
s3Mock
.on(GetObjectCommand, {
Bucket: 'dummyBucket',
Key: 'dummyKey',
})
.resolves({
$metadata: {
httpStatusCode: 400,
},
Body: fs.createReadStream(path.resolve(__dirname, './example.txt')),
})
const result = await main()
expect(result).toBe('NG')
})
})
describe('S3のオブジェクトを取得できない', () => {
it('成功', async () => {
s3Mock
.on(GetObjectCommand, {
Bucket: 'dummyBucket',
Key: 'dummyKey',
})
.rejects('S3のオブジェクトを取得できない')
// throwのチェックは以下の書き方が良さそう...
const error = new Error('S3のオブジェクトを取得できない')
await expect(main()).rejects.toEqual(error)
})
})
今まで、JestのmockImplementation
やmockImplementationOnce
を多用して、以下のようにAWS-SDKのクライアントをMockしていた時と比べるとだいぶコードが見やすくなった。
これは素晴らしい。。。
// Mockライブラリを使う前
jest.mock('@aws-sdk/client-s3')
mocked(S3Client).mockImplementation((): any => {
return {
send: (command: any): GetObjectCommandOutput => {
return {
$metadata: {
httpStatusCode: 200,
},
Body: fs.createReadStream(path.resolve(__dirname, './example.txt')),
}
},
}
})
// Mockライブラリを使った後
const s3Mock = mockClient(S3Client)
s3Mock
.on(GetObjectCommand, {
Bucket: 'dummyBucket2',
Key: 'dummyKey2',
})
.resolves({
$metadata: {
httpStatusCode: 200,
},
Body: fs.createReadStream(path.resolve(__dirname, './example.txt')),
})
特に便利だと思ったのが、.on
にCommand系のパラメータを設定できるところ。
ドキュメントには「Order of mock behaviors」という見出しで書かれていましたが、要は.on
のパラメータを見て、Mockの順番を変更できる機能。以下に例を書いてみる。
例(S3のバケットからオブジェクトを取得する処理2)
// src/main.ts
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
export async function main() {
try {
const s3 = new S3Client({ region: 'ap-northeast-1' })
// 1回目は、dummyBucket1からオブジェクトを取得
const obj1 = await s3.send(
new GetObjectCommand({
Bucket: 'dummyBucket1',
Key: 'dummyKey1',
}),
)
if (obj1.$metadata.httpStatusCode && obj1.$metadata.httpStatusCode >= 400) {
return 'NG'
}
// 2回目は、dummyBucket2からオブジェクトを取得
const obj2 = await s3.send(
new GetObjectCommand({
Bucket: 'dummyBucket2',
Key: 'dummyKey2',
}),
)
if (obj2.$metadata.httpStatusCode && obj2.$metadata.httpStatusCode >= 400) {
return 'NG'
}
return 'OK'
} catch (error) {
throw error
}
}
テスト例(S3のバケットからオブジェクトを取得する処理2)
// test/main.spec.ts
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { mockClient } from 'aws-sdk-client-mock'
import fs from 'fs'
import path from 'path'
import { main } from '../src/main'
const s3Mock = mockClient(S3Client)
beforeEach(() => {
s3Mock.reset()
})
describe('S3のオブジェクトを取得できる', () => {
it('成功', async () => {
s3Mock
.on(GetObjectCommand, {
Bucket: 'dummyBucket2',
Key: 'dummyKey2',
})
.resolves({
$metadata: {
httpStatusCode: 200, // dummyBucket2から取得した結果
},
Body: fs.createReadStream(path.resolve(__dirname, './example.txt')),
})
.on(GetObjectCommand, {
Bucket: 'dummyBucket1',
Key: 'dummyKey1',
})
.resolves({
$metadata: {
httpStatusCode: 200, // dummyBucket1から取得した結果
},
Body: fs.createReadStream(path.resolve(__dirname, './example.txt')),
})
const result = await main()
expect(result).toBe('OK')
})
})
複数回クライアントをMockする時に、どの結果がどのMockなのかが、とても見やすくなった。
さいごに
個人的な感想だが、最近はAWS CDKを使うようになってLambdaのコード等もTypeScriptを使って書く機会が増えた。
TypeScriptは、テストコードを書く時に(Jestを使って)自力Mockも可能だが、やはりMock用のライブラリを使ったほうがコードが見やすくなるので、積極的に使っていきたい。