TypeScript + Jestでaws-sdkをmockする

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

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

動作確認元のDynamoDBテーブル定義や詳細は[この記事](https://www.ooooouchi.info/entry/2020/09/04/094500
)を参照してください。
CloudFormationの内容と実プロジェクトのリポジトリを記載しています。

## Jest
https://jestjs.io/
Facebook製のテストフレームワークです。
概要や基本的な書き方は[この記事](https://qiita.com/chimame/items/e97883fd46b67529d59f)が参考になります。

## Jest導入
```sh
yarn add jest @types/jest ts-jest -D
```

## jest.config.js作成
```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"],
};
```

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

```TypeScript
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

```ts
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化しています。

## 実行
```shell
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を使用したロジックの動作確認が実施できるようになって結構テンションがあがりました🍣