HAPPY HACKING Oouchi's BLOG

PSE(ポンコツエンジニア)の技術ブログ

TypeScript + Jestでaws-sdkをmockする

この記事について

最近よくTypeScriptでAWSのリソース操作を行うのですが、動作確認を行うたびにリソースを作ったり消したりするのが面倒くさいと感じていました。
ましてやユニットコードなんて書いてもその時のリソース状況にがっつり依存するなあ・・・と少しナイーブになっていました。

そんな中、最近やっとaws-sdkのモック化に成功したのでメモとして残しておきます。

プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発

プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発

  • 作者:Boris Cherny
  • 発売日: 2020/03/16
  • メディア: 単行本(ソフトカバー)

Jest

https://jestjs.io/
Facebook製のテストフレームワークです。 概要や基本的な書き方はこの記事が参考になります。

Jest導入

yarn add jest @types/jest ts-jest -D

jest.config.js作成

module.exports = {
    roots: ["<rootDir>/test", "<rootDir>/src"],
    transform: {
        "^.+\\.tsx?$": "ts-jest",
    },
    testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"],
    moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
};

本題

CloudFormation定義

今回は以下の定義を使用したテーブルを対象とします。

AWSTemplateFormatVersion: 2010-09-09
Description: Init DynamoDb.
Parameters:
  Env:
    Type: String
    AllowedValues:
      - prod
      - stg
      - dev
  SystemName:
    Type: String
    Default: Sample
Resources:
  SampleTable:
    Type: "AWS::DynamoDB::Table"
    Properties:
      TableName: SampleTable
      AttributeDefinitions:
        - AttributeName: SampleKey
          AttributeType: S
      KeySchema:
        - AttributeName: SampleKey
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      Tags:
        - Key: Name
          Value: !Join ["-", [!Ref Env, !Ref SystemName, SampleTable]]
Outputs:
  SampleTableArn:
    Value: !GetAtt SampleTable.Arn

実装

今回はDynamoDBのテーブルをscanし、項目の数によって返却する値を変えるシンプルなコードを書きます。
src/index.ts

import { DynamoDB } from "aws-sdk";
export const handler = async (event: any) => {
  let scanItem = await new DynamoDB()
    .scan({ TableName: "SampleTable", Select: "ALL_ATTRIBUTES" })
    .promise();
  if (scanItem.Count === 1) {
    return "1";
  } else if (scanItem.Count === 2) {
    return "2";
  }
  return "etc";
};

テストコード実装

jest.config.jsで定義したパスにファイルを作成します。
test/scan-dynamodb.test

import { DynamoDB } from "aws-sdk";
import { mocked } from "ts-jest/utils";

import { handler } from "../src/index";

jest.mock("aws-sdk");

describe("scan test", () => {
  it("Count => 1 : return 1", async () => {
    let data = {
      Items: [{ value: "Value1", SampleKey: "key1" }],
      Count: 1,
      ScannedCount: 1,
    };
    mocked(DynamoDB).mockImplementationOnce((): any => {
      return {
        scan: (_param: any, callback: any) => {
          return {
            promise: () => {
              return data;
            },
          };
        },
      };
    });

    const result = await handler({});
    expect(result).toEqual("1");
  });
  it("Count => 2 : return 2", async () => {
    let data = {
      Items: [
        { value: "Value1", SampleKey: "key1" },
        { value: "Value2", SampleKey: "key2" },
      ],
      Count: 2,
      ScannedCount: 2,
    };
    mocked(DynamoDB).mockImplementationOnce((): any => {
      return {
        scan: (_param: any, callback: any) => {
          return {
            promise: () => {
              return data;
            },
          };
        },
      };
    });

    const result = await handler({});
    expect(result).toEqual("2");
  });
  it("Count => 3 : return etc", async () => {
    let data = {
      Items: [
        { value: "Value1", SampleKey: "key1" },
        { value: "Value2", SampleKey: "key2" },
        { value: "Value3", SampleKey: "key3" },
      ],
      Count: 3,
      ScannedCount: 3,
    };
    mocked(DynamoDB).mockImplementationOnce((): any => {
      return {
        scan: (_param: any, callback: any) => {
          return {
            promise: () => {
              return data;
            },
          };
        },
      };
    });

    const result = await handler({});
    expect(result).toEqual("etc");
  });
});

mocked(DynamoDB).mockImplementationOnceでDynamoDBのscan関数をmock化しています。

実行

yarn jest
yarn run v1.22.4
 PASS  test/scan-dynamodb.test.ts (10.408 s)
  scan test
    √ Count => 1 : return 1 (5 ms)
    √ Count => 2 : return 2
    √ Count => 3 : return etc (1 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        11.903 s
Ran all test suites.
Done in 13.56s.

しっかり通りました。

所感

できるまで非常に時間がかかりましたが、やってみると意外とシンプルに完結できることがわかりました。
実際にデータを用意せずにaws-sdkを使用したロジックの動作確認が実施できるようになって結構テンションがあがりました🍣

最後に

今回使用したソース含むプロジェクトはこちらです。
CloudFormationの定義ファイルも用意しているので実際のリソースを使用した確認も簡単にできると思います!

github.com