LoginSignup
3
1

More than 1 year has passed since last update.

AWS JavaScript SDK v3 で jest を使う。aws-sdk-client-mock(-jest) の利用記録

Posted at

背景

AWS 業務も一段落したので、AWS SDK for JavaScript V3 での jest の利用記録を残しておく

方針

色々試してみて、最終的に以下とした

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 業務とはバイバイなのです。
次の業務はなんになるのやら :thinking:

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1