1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

jestとaws-sdk-client-mockの使い方

Posted at

Lambdaのテストを書くときにいつも書き方を忘れるのでメモします
jestとaws-sdk-client-mockを使います

環境整備

npm install -D aws-sdk-client-mock jest babel-jest @babel/core @babel/preset-env jest-html-reporters
bable.config.js
module.exports = {
    presets: [
        [
            "@babel/preset-env"
        ]
    ],
}
jest.config.js
module.exports = {
    clearMocks: true,
    collectCoverage: true,
    coverageDirectory: "coverage",
    coveragePathIgnorePatterns: ["/node_modules/"],
    reporters: [
        'jest-html-reporters'
    ],
    testEnvironment: "node",
    moduleFileExtensions: ["js", "mjs", "json", "node"],
    testRegex: ".*test.js",
    testPathIgnorePatterns: ["/node_modules/"],
    transform: {
        "^.+\\.m?js?$": "babel-jest",
    },
    setupFiles: ["<rootDir>/jest.setup.js"],
};
jest.setup.js
// 最初に呼び出したいスクリプトを設定
// 環境変数の設定とかお好みで
process.env.TABLE_NAME = "hoge";
process.env.BUCKET_NAME = "hoge"
process.env.AWS_ACCESS_KEY_ID = "dummy";
process.env.AWS_SECRET_ACCESS_KEY = "dummy";
process.env.AWS_REGION = "us-west-2";

DynamoDBからItemを取得するコード

import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

const initDynamoClient = () => {
    const marshallOptions = {
        removeUndefinedValues: true,
    };
    const translateConfig = { marshallOptions };
    const DynamoDBclient = new DynamoDBClient({
        region: 'ap-northeast-1'
    });
    
    const dynamo = DynamoDBDocumentClient.from( DynamoDBclient, translateConfig );
    return dynamo;
}

const response = (statusCode, body) => {
    return {
        statusCode: statusCode,
        headers: {
            "Access-Control-Allow-Headers" : "Content-Type",
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "GET"
        },
        body: JSON.stringify(
            body
        ),
    };
};

export const handler = async (event) => {
    try {
        const dynamo = initDynamoClient();
        const id = event.pathParameters.id;
        const dynamo_data = await dynamo.send(
            new GetCommand({
                TableName: "Dynamo_test",
                Key: {
                    id: id,
                },
            })
        );
        if (dynamo_data.Item === undefined) {
            return response(404, {"message": "not found."});
        } else {
            return response(200, dynamo_data.Item);
        }
    } catch (error) {
        console.log(error);
        return {
            statusCode: 500,
            headers: {
                "Access-Control-Allow-Headers" : "Content-Type",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET"
            },
            body: JSON.stringify({"message": "500err"}),
        };
    }
};

テストコード

import { handler as getItem } from "../get_dynamo.mjs";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
import { mockClient } from "aws-sdk-client-mock";

const ddbMock = mockClient(DynamoDBDocumentClient)

describe("handler get item", () => {
    beforeEach(() => {
        ddbMock.reset()
    })

    it("get item test", async () => {
        const event = {
            "pathParameters": {
                "id": "test"
            }
        };
        const expectValue = {
            Item: { id: "test", value: "memo" }
        };
        // すべてのGetCommandの結果を同じにしたい場合resolvesでOK
        // 1回目の呼び出しと2回目のレスポンスを変更したい場合resolvesOnceを使う
        ddbMock.on(GetCommand).resolves(expectValue)
        const result = await getItem(event)
        expect(result).toEqual({
            statusCode: 200,
            headers: {
                "Access-Control-Allow-Headers" : "Content-Type",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET"
            },
            body: JSON.stringify(
                {id:"test",value:"memo"}
            )
        })
        const callsOfGet = ddbMock.commandCalls(GetCommand);
        // コマンドが何回呼ばれたか検証
        expect(callsOfGet.length).toBe(1);
        // 指定されたパラメータで呼ばれたか検証
        expect(callsOfGet[0].args[0].input).toEqual({
            TableName: "Dynamo_test",
            Key: {
                "id": "test"    
            },
        });
    }

    it("item not found test", async () => {
        const event = {
            "pathParameters": {
                "id": "notfoundID"
            }
        };
        const expectValue = {
            Item: undefined
        };
        ddbMock.on(GetCommand).resolves(expectValue)
        const result = await getItem(event)
        expect(result).toEqual({
            statusCode: 404,
            headers: {
                "Access-Control-Allow-Headers" : "Content-Type",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET"
            },
            body: JSON.stringify(
                {"message": "not found."}
            ),
        });
    })

    it("handler response test", async () => {
        const event = {
            "pathParameters": {
                "id": "test"
            }
        };
        const expectValue = {
            Item: { id: event.pathParameters.id, value: "memo" }
        };
        ddbMock.on(GetCommand).resolves(expectValue)
        const result = await getItem(event)
        expect(result).toEqual({
            statusCode: 200,
            headers: {
                "Access-Control-Allow-Headers" : "Content-Type",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET"
            },
            body: JSON.stringify(
                expectValue.Item
            ),
        });
    })
})

describe("handler get item error test", () => {
    beforeEach(() => {
        ddbMock.reset()
    })

    it("handler error test", async () => {
        const event = {
            "pathParameters": {
                "id": "test"
            }
        };
        const expectValue = "ERR"
        // errを返すときはrejects
        ddbMock.on(GetCommand).rejects(expectValue)
        const result = await getItem(event)
        expect(result).toEqual({
            statusCode: 500,
            headers: {
                "Access-Control-Allow-Headers" : "Content-Type",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET"
            },
            body: JSON.stringify(
                {"message": "500err"}
            ),
        });
    })
})

おまけ

時間を固定で設定する

beforeEach(async () => {
    const fixed = new Date("2024-01-01T00:00:00");
    jest.useFakeTimers().setSystemTime(fixed.getTime());
});

モジュールを雑にモック

// fsモジュール全体をモック化
jest.mock("node:fs", () => ({
    ...jest.requireActual("node:fs"),
    writeFileSync: jest.fn(),
    existsSync: jest.fn(),
    readFileSync: jest.fn(),
}));

テストコード内で返り値設定

// ディレクトリが存在すると仮定
fs.existsSync.mockImplementation(() => true);
fs.readFileSync.mockImplementation(() =>
    JSON.stringify([
        {
            name: "検証",
            Id: "00001",
        }
    ])
);
fs.writeFileSync.mockImplementation(() => {});

参考URL

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?