7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

こんなのがあったのか!? AWS SDK v3 Client mock

Last updated at Posted at 2021-11-16

はじめに

結構前の話になりますが、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のmockImplementationmockImplementationOnceを多用して、以下のように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用のライブラリを使ったほうがコードが見やすくなるので、積極的に使っていきたい。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?