この記事について
最近よくTypeScriptでAWSのリソース操作を行うのですが、動作確認を行うたびにリソースを作ったり消したりするのが面倒くさいと感じていました。
ましてやユニットコードなんて書いてもその時のリソース状況にがっつり依存するなあ・・・と少しナイーブになっていました。
そんな中、最近やっとaws-sdkのモック化に成功したのでメモとして残しておきます。
プログラミング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の定義ファイルも用意しているので実際のリソースを使用した確認も簡単にできると思います!