aws-sdk-client-mockとJestを使ったLambda関数の単体テスト

AWS

導入・背景

SDK for JavaScript v3を使ってLambda関数を書く機会があり、それに伴ってテストコードはどう実装すべきか調べました。

どうやらaws-sdk-client-mockとJestを使うことで比較的簡単に単体テストを作成できるようなので、基本的な使い方についてまとめていきたいと思います。

  • aws-sdk-client-mockを使ってみたい方
  • これからAWS SDK for JavaScript v3を使ったLambdaのテストコードを書こうと思っている方

テスト関連のパッケージインストール

今回はTypeScriptコードをコンパイル無しにテストする想定です。

Jestの環境構築は公式サイトを参考に作成しています。

npm install --save-dev jest ts-jest @types/jest aws-sdk-client-mock
  1. jest・・・・・・・Jest本体
  2. ts-jest・・・・・・TypeScriptでJestを使いたい場合、JestをTSに対応させるために必要なパッケージ
  3. @types/jest・・・・Jest APIの型定義パッケージ
  4. aws-sdk-client-mock・・・AWS SDK for JavaScript v3のClientをMockするパッケージ

Lambdaのテスト

実行コード

DynamoDBからUserデータをGetする実行コードをテストしてみます。

import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb"

export const handler = async (event: {userId: string}) => {
  const userId = event.userId;
  const dynamodb = new DynamoDBClient({});
  const ddb = DynamoDBDocumentClient.from(dynamodb)

  try {
    const result = await ddb.send(
      new GetCommand({
        TableName: "users",
        Key: {
          user_id: userId,
        },
      })
     );
     return result;
  } catch (error) {
    console.log("failed get user", error)
    throw error;
  }
}

aws-sdk-client-mockとJestを使ったテストコード

import { mockClient } from "aws-sdk-client-mock"
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb"
import { handler } from "./index"

const ddbMock = mockClient(DynamoDBDocumentClient)

describe("get user data", () => {
  beforeEach(() => {
    ddbMock.reset()
  })

  it("should get user names", async () => {
    const event = {
      userId: '0'
    }

    const expectValue = {
      Item: { userId: event.userId, name: "Bob" }
    }

    ddbMock.on(GetCommand).resolves(expectValue)

    const result = await handler(event)
    expect(result.Item).toStrictEqual(expectValue.Item)
  })
})

コード解説

Mockを作成

import { mockClient } from "aws-sdk-client-mock"
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb"

const ddbMock = mockClient(DynamoDBDocumentClient)

aws-sdk-client-mockのmockClientを使って、DynamoDBDocumentClientをmock化しておきます。

モックをリセット

beforeEach(() => {
  ddbMock.reset()
})

今回のテストケースは1つですが、複数のテストを実行する場合、各テストの実行前にmockの中身をリセットしておきます。

この記述が無いと1つ前のテストのデータが保持されたまま次のテストが実行されるなどエラーの原因となります。

const expectValue = {
  Item: { userId: event.userId, name: "Bob" }
}

ddbMock.on(GetCommand).resolves(expectValue)

今回はGetCommandを使うので、mockでGetCommandが実行された場合に、返却される値をresolvesの引数に設定します。

結果をチェック

const result = await handler(event)
expect(result.Item).toStrictEqual(expectValue.Item)

mock化をした実行コードにeventを渡し、実行結果が期待しているデータと一致するかをチェックします。

期待する値するかどうかはJestのtoStrictEqual(Jestが用意しているMachaer)を使ってチェックできます。

呼び出し時の引数をチェック

先ほどは実行コードで返却された値をテストしましたが、GetCommandに渡す引数が期待通りかをチェックしたい場合もあると思います。その場合は以下のようにしてテストします。

// 実行されたGetCommandの情報を取得
const calledGetCommand = ddbMock.commandCalls(GetCommand);

// GetCommand実行時に渡された引数が期待通りかチェック
expect(calledGetCommand[0].args[0].input).toStrictEqual({
  TableName: 'users',
  Key: { user_id: event.userId}
})

aws-sdk-client-mockに対応したJestのCustom Machaerを使ってテストする

例えばmockが実行したCommandは何か?のような観点でテストを行いたい場合は、以下のパッケージをインストールします。

jest-node18 % npm install -D aws-sdk-client-mock-jest
import 'aws-sdk-client-mock-jest' // import追加

・・・省略・・・

expect(ddbMock).toHaveReceivedCommand(GetCommand)

aws-sdk-client-mockのmockに対応したJestのカスタムマッチャーを使うことで、mockしたClientがGetCommandを呼び出したかテストできます。

テストコード全文

import { mockClient } from "aws-sdk-client-mock"
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb"
import { handler } from "./index"
import 'aws-sdk-client-mock-jest'

const ddbMock = mockClient(DynamoDBDocumentClient)

describe("get user data", () => {
  beforeEach(() => {
    ddbMock.reset();
  })

  it("should get user names", async () => {
    const event = {
      userId: '0'
    }

    const expectValue = {
      Item: { userId: event.userId, name: "Bob" }
    }

    ddbMock.on(GetCommand).resolves(expectValue);

    const result = await handler(event);
    expect(result.Item).toStrictEqual(expectValue.Item);

    const calledGetCommand = ddbMock.commandCalls(GetCommand);
    expect(calledGetCommand[0].args[0].input).toStrictEqual({
      TableName: 'users',
      Key: { user_id: event.userId}
    })

    expect(ddbMock).toHaveReceivedCommand(GetCommand)
  })
})

まとめ

今回はaws-sdk-client-mockとJestを使ってSDK for JavaScript v3を使ったLambdaのテストコードを作成しました。aws-sdk-client-mockを使うことで1行でClientをmock化でき、テストコードを簡単に作成できます。DynamoDB以外のサービスでもclientをmockし、コードを実行、各種Commandや結果をテストする流れは同じなので、一度覚えてしまえば他サービスのテストも簡単に行えると思います。

参考

https://github.com/m-radzikowski/aws-sdk-client-mock#dynamodb-documentclient

https://jestjs.io/ja/docs/getting-started

無料相談実施中
AWSを使用したサーバーレスアプリケーションの構築
サーバーレス開発内製化、AWSへの移行等
様々な課題について無料でご相談お受けいたします。
最新情報をチェックしよう!