背景
AWS 業務も一段落したので、AWS SDK for JavaScript V3 での jest の利用記録を残しておく
方針
色々試してみて、最終的に以下とした
-
jest で
- TypeScript なら ts-jest がよさげ
-
AWS JavaScript SDK v3 には
-
aws-sdk-client-mock で Mock を
- jest 連携は aws-sdk-client-mock-jest を
-
aws-sdk-client-mock で Mock を
mock 概要
client を以下の要領で Mock して
mock 例
const snsMock = mockClient(SNSClient);
const userPoolMock = mockClient(CognitoIdentityProviderClient);
const dynamoDBdocMock = mockClient(DynamoDBDocumentClient);
const s3Mock = mockClient(S3Client);
const dynamoDB = new DynamoDBClient({});
const dynamoDBMock = mockClient(dynamoDB);
dynamoDBdocMock.onAnyCommand().resolves({}); // 全Command に対して定義
dynamoDBdocMock
.on(GetCommand) // GetCommand 全部
.resolves({
Item: [],
})
.on(GetCommand, { // GetCommand Property の部分一致
IndexName: "index-Age",
})
.resolves({
Item: [],
});
aws-sdk-client-mock-jest を使うと楽になる
mtaches
import 'aws-sdk-client-mock-jest';
expect(snsMock).toHaveReceivedCommand(PublishCommand);
expect(snsMock).toHaveReceivedCommandTimes(PublishCommand, 2);
expect(snsMock).toHaveReceivedCommandWith(PublishCommand, {Message: 'My message'});
expect(snsMock).toHaveReceivedNthCommandWith(2, PublishCommand, {Message: 'My message'});
利用記録
jest/ts-jest 準備
情報は多いので、使えるようにするだけのこと
jest & ts-jest install
npm -D install jest ts-jest @types/jest
aws-sdk-client-mock/aws-sdk-client-mock-jest 準備
aws-sdk-client-mock(-jest)
npm install -D aws-sdk-client-mock aws-sdk-client-mock-jest
以下、Usage に従うだけ
lib mock
lib-dynamodb を使うので、以下のように Mock する
import {
GetUserCommand,
CognitoIdentityProviderClient,
} from "@aws-sdk/client-cognito-identity-provider";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
DynamoDBDocumentClient,
DeleteCommand,
GetCommand,
PutCommand,
QueryCommand,
ScanCommand,
UpdateCommand,
} from "@aws-sdk/lib-dynamodb";
import {
S3Client,
CreateMultipartUploadCommand,
CompleteMultipartUploadCommand,
DeleteObjectsCommand,
PutObjectCommand,
UploadPartCommand,
PutPublicAccessBlockCommand,
} from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { mockClient } from "aws-sdk-client-mock";
import "aws-sdk-client-mock-jest";
const dynamoDB = new DynamoDBClient({});
const dynamoDBMock = mockClient(dynamoDB);
const userPoolMock = mockClient(CognitoIdentityProviderClient);
const dynamoDBdocMock = mockClient(DynamoDBDocumentClient);
const s3Mock = mockClient(S3Client);
各Command の返り値の設定
Command Return
beforeEach(async () => {
userPoolMock.reset();
userPoolMock.onAnyCommand().resolves({});
dynamoDBdocMock.reset();
dynamoDBdocMock.onAnyCommand().resolves({});
s3Mock.reset();
s3Mock.onAnyCommand().resolves({});
});
test("テスト", async () => {
userPoolMock
.on(GetUserCommand)
.resolves({ UserAttributes: [] });
dynamoDBdocMock.onAnyCommand().resolves({}); // 全Command に対して定義
dynamoDBdocMock
.on(GetCommand) // GetCommand 全部
.resolves({
Item: [],
})
.on(GetCommand, { // GetCommand Property の部分一致
IndexName: "index-Age",
})
.resolves({
Item: [],
});
s3Mock.on(CompleteMultipartUploadCommand).resolves(uploadInfo);
s3Mock.on(CreateMultipartUploadCommand).resolves({ UploadId: "1" });
s3Mock.on(UploadPartCommand).resolves({ ETag: "1" });
});
同一コマンドで複数定義する場合は、先に広い定義をしておく。
Wider Command matchers must be declared first, otherwise, they will take precedence over previous ones.
確認例
how to use 'expect'
expect(await lambdaHandler(events, context))
.toStrictEqual(returnObjectExpected);
// QueryCommand の CommandInput を取得
const callsOfQuery = dynamoDBdocMock.commandCalls(QueryCommand);
expect(callsOfQuery).toHaveLength(1); // QueryCommand の Calls 数
// Mock 単位で *Command の Calls 順番毎に、input の確認
// aws-sdk-client-mock-jest を利用した場合。
expect(dynamoDBdocMock).toHaveReceivedNthCommandWith(1, QueryCommand, {
TableName: "target",
IndexName: "index-target",
ExpressionAttributeNames: {
"#PK": "partition key",
"#SK": "range key",
},
ExpressionAttributeValues: {
":PK": "pk value",
":startAfter": `${timestampBegin}`,
":endBefore": `${timestampEnd}`,
},
KeyConditionExpression:
"#PK = :PK AND ( #SK BETWEEN :startAfter AND :endBefore )",
Limit: limit,
ScanIndexForward: false,
});
// aws-sdk-client-mock-jest を利用しない場合、以下のようなやり方も。
// property の部分確認が必要な場合にはこちらも必要
const queryCommandInput = callsOfQuery[0].args[0].input; // 実際の input を取得
const queryItems = queryCommandInput.Items; // 取得データ
expect(queryItems[0].timestamp.toString()).toMatch(/^\d{13}$/); // 正規表現での確認
躓きの記録と対処
Mock が動かない
- 前提
Sam build/deploy する為に、Lambda 毎に階層化して実装してた。
この際、test.ts は 最上位に test/ フォルダを用意して作成していたところ、Mock ではなく 実 AWS 稼働になってた。 - 原因
正直よくわかってない。 - 解決策
Mock の Emulate 順序によるのかと考えて、Import などの位置を前後しても特に変わらなかったが、
たまたま、Lambda と同階層に test/ フォルダを移動したら Mock が効くようになった。
期待値が違う
- 原因
Mock の resolves の定義順序ミス - 解決策
広域⇒狭域へを見直した
toBe から toStrictEqual へ
failed メッセージに従って対処するのみ
参考
- jest
- aws-mock
あとがき
一旦 AWS 業務とはバイバイなのです。
次の業務はなんになるのやら