発生したこと
DynamoDBを利用するLambda関数をローカルでテストすることになりました。
ランタイムはNode.js、コードはTypeScriptで書いたものをJavaScriptにコンパイルします。
その中でDynamoDBをMock化する必要が発生しました。
利用したライブラリはaws-sdk-mock-client
です。
しかし、ドキュメントや記事どおりにMock化したはずなのですが、設定したレスポンスが返ってきません。
import { tergetFunction } from "./terget.js";
import { mockClient } from "aws-sdk-mock-client";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dnamodb";
const ddbMock = mockClient(DynamoDBDocumentClient);
test("sample test", async () => {
const responseStub = {
'$metadata": {
"httpStatusCode": 200,
"requestId": "stub id",
}
};
ddbMock.on(PutCommand).resolves(responseStub);
const result = await targetFunction();
});
ドキュメントや記事に記載のテストコードと私の書いたテストコードにはそれぞれ僅かな違いがありますが、実行に問題はないはずでした。
原因
上で述べたように、ソースコードはTypeScriptをコンパイルしています。
その際、テスト対象とテストコードの間に仕様の差異が発生していました。
JavaScriptにはCommonJSとECMAScriptという2つの仕様があります。
テストコードはECMAScriptの記法で書かれていましたが、ソースコードはCommonJSの記法にコンパイルされていました。
CommonJSとECMAScriptには互換性がないため、DynamoDBがMock化できなかったわけです。
解決方法
TypeScriptのコンパイルオプションとテストコードをECMAScriptかCommonJSのどちらかに合わせればよいです。
私自身はECMAScriptしか扱ったことがないため、本記事ではそちらに合わせます。
jest自体はCommonJSで動作するようです。実行時にメモリ上でCommonJSにトランスパイルしているのだとか。
tsconfig.jsonとpackage.jsonのオプションを変更します。
{
"compilerOptions": {
"target": "ES6",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"baseUrl": "./src",
"paths": {
"validators/*": ["validators/*"]
}
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
変更する箇所は"target"
"module"
"moduleResolution"
です。
これらをcommonjsのものから適宜プロジェクトに適した値に変更し、再度npm run build
を行えばECMAScriptの記法でトランスパイルされます。
おまけ
一度、ts-jestを用いてTypeScriptのままテストを行うことも検討しました。
(コンパイルに必要な記述とテスト実行に必要な記述が競合したためJavaScriptでの実行に戻りました)
その際に躓いた点も記載しておきます。
Lambda関数が受け取る引数にはAPIGatewayProxyHandler型などを設定します。
実行上はeventしか渡していませんが、contextとcallbackが必須なためテストが通りません。
このcontextはLambda関数の実行時に渡されるインスタンスの情報などだそうです。
ドキュメントなどで型の詳細を調べて適当にスタブをjsonファイルで作成し、fsを用いて読み込むなどで解決できます。