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

Serverless Framework + TypeScript(esbuild)を JestでUnitテストしてみる

Last updated at Posted at 2025-02-13

前記事(Serverless Framework + TypeScript(esbuild)で ローカル開発環境構築と、AWS(API Gateway + Lambda + DynamoDB)へのデプロイをしてみる)で作ったModelプログラムをJestを使ってUnitテストしてみます。

事前準備

プロジェクトルートのディレクトリに移動します。

Jestをグローバルにインストール

npm i -g jest

関連するライブラリをインストール

npm i -D jest @types/jest
npm i -D esbuild-jest

Jestの設定ファイルを作成

jest.config.js
module.exports = {
  testEnvironment: 'node',        // テスト環境
  transform: {
    "^.+\\.tsx?$": "esbuild-jest" // transformerにesbuild-jest
  },
  globalSetup: "./setupEnv.ts",   // テスト用に環境変数ファイルを取得
};

テスト用の環境変数ファイルを作成

localで実行するようにします。

setupEnv.ts
export default (): void => {
    console.log("\nSetup test environment");
    process.env.ENV = "local";
    process.env.TZ = "Asia/Tokyo";
    console.log("process.env.ENV", process.env.ENV);
    console.log("process.env.TZ", process.env.TZ);
    return;
};

src/model/User.tsファイルを編集

src/model/User.ts(修正前)
  :
  :
    constructor() {
        if (global.dynamodbClient) {
            this.client = global.dynamodbClient;
        } else {
            this.client = new DynamoDBClient({
                region: process.env.AWS_REGION,
            });
        }
        this.documentClient = DynamoDBDocumentClient.from(this.client);
    }
  :
  :

    ↓ ↓ ↓ ↓ ↓ ↓

src/model/User.ts(修正後)
  :
  :
    constructor() {
        if (global.dynamodbClient) {
            this.client = global.dynamodbClient;
        } else {
            if (process.env.ENV === "local") {
                this.client = new DynamoDBClient({
                    region: process.env.AWS_REGION,
                    endpoint: "http://localhost:8000", // <===== これを指定しないと、DynamoDBアクセス時、AWS profile(./aws/credentials)のデフォルト設定にアクセスしてしまうので注意!
                });
            } else {
                this.client = new DynamoDBClient({
                    region: process.env.AWS_REGION,
                });
            }
        }
        this.documentClient = DynamoDBDocumentClient.from(this.client);
    }
  :
  :

しばらくの間「endpoint: "http://localhost:8000" 」をつけずにJest実行していて、リモートのAWSのDynamoDBにアクセスしていたので、ここで数時間ハマりました。

src/model/User.tsファイルにテストで使う関数を追加

何に使うかと言うと、ユーザーデータを更新したあとに、確かに更新されたかどうかDBからデータを取得する為に利用します。

src/model/User.ts
  :
  :
    /**
     * IDからユーザーを取得
     * @param id
     * @returns
     */
    getById = async (id: string) => {
        let ret;
        const params: QueryCommandInput = {
            TableName: this.userTableName,
            KeyConditionExpression: "#key = :value",
            ExpressionAttributeNames: {
                "#key": "id",
            },
            ExpressionAttributeValues: {
                ":value": { S: id },
            },
        };

        try {
            const command = new QueryCommand(params);
            const data = await this.client.send(command);
            if (data && data.Items) {
                for (const item of data.Items) {
                    ret = item;
                    break;
                }
            }
        } catch (err) {
            console.error("Unable to query. Error users(1.1):", JSON.stringify(err, null, 2));
            throw new Error(`Unable to query. Error users(1.1): ${err.message}`);
        }
        return ret;
    };
  :
  :

Unitテストを書いてみる(ここが本題)

mkdir test # テストスクリプト用ディレクトリを作成
cd test

ローカルのDynamoDBに対して、以下を順番に実施します。

  1. User登録に関するテスト
  2. User取得に関するテスト
  3. User更新に関するテスト
  4. User削除に関するテスト

ソースコードの補足説明

describe : テストのグルーピングに使います。
it : testと同じ意味で、テスト関数になります。

test/User.test.js
import { User } from "../src/model/User"; // テストするModelを読み込みます
import { setTimeout } from "node:timers/promises"; // スリープ用のsetTimeoutを読み込みます

const USER_SIGNATURE = "test1234"; // 今回のSignature(本番データと被らなければなんでも良いです)

describe("User登録に関するテスト", () => {
    it("新規登録のテスト", async () => {
        const user = new User();

        // ユーザー作成
        const createdUserId = await user.createUser(USER_SIGNATURE, "山田", "男性");

        // SignatureからユーザーIDを取得
        const getUserId = await user.getIdBySignature(USER_SIGNATURE);

        // 作成したユーザーが存在するかチェック
        expect(createdUserId).toBe(getUserId);
    });

    it("二重登録エラーのテスト", async () => {
        const user = new User();

        // わざと同一のSignatureでユーザーを登録してエラーが出るかチェック
        try {
            const result = await user.createUser(USER_SIGNATURE, "山田", "男性");
        } catch (e) {
            // 許容するエラーメッセージ(Transaction cancelled ~)かチェック
            expect(e.message).toContain("Unable to put item. Error users(2): Transaction cancelled");
        }
    });
});

describe("User取得に関するテスト", () => {
    it("一覧取得テスト", async () => {
        const user = new User();

        // ユーザー一覧を取得
        const users = await user.getAll();

        // ユーザー一覧が存在する(0件超か)かチェック
        expect(users.length).toBeGreaterThan(0);
    });
});

describe("User更新に関するテスト", () => {
    it("更新テスト", async () => {
        const user = new User();

        // SignatureからユーザーIDを取得
        const getUserId = await user.getIdBySignature(USER_SIGNATURE);

        // 更新後のユーザー情報
        const req = {
            userId: getUserId,
            body: {
                user: {
                    name: "鈴木",
                    gender: "女性",
                },
            },
        };

        // ユーザー情報の更新
        await user.updateUserById(req);

        // データ更新されるまで1秒待つ(ローカルのDynamoDBが更新に時間かかるので待つようにしました)
        await setTimeout(1000);

        // 更新されたユーザー情報をDBから取得
        const updatedUser = await user.getById(getUserId);

        // 正しく更新されたかチェック
        expect(updatedUser.name.S).toBe("鈴木");
        expect(updatedUser.gender.S).toBe("女性");
    });
});

describe("User削除に関するテスト", () => {
    it("削除テスト", async () => {
        const user = new User();

        // SignatureからユーザーIDを取得
        const getUserId = await user.getIdBySignature(USER_SIGNATURE);

        // 削除するユーザー情報
        const req = {
            headers: {
                signature: USER_SIGNATURE,
            },
            userId: getUserId,
        };

        // ユーザー情報の削除
        await user.deleteUserById(req);

        // データ削除されるまで1秒待つ(ローカルのDynamoDBが削除に時間かかるので待つようにしました)
        await setTimeout(1000);

        // 正しく削除されたかチェック(ユーザー情報が取れていない(空白)ならOK)
        const checkUserId = await user.getIdBySignature(USER_SIGNATURE);
        expect(checkUserId).toBe("");
    });
});

テストを実行してみる

$ jest  # 入力してEnter
Determining test suites to run...
Setup test environment
process.env.ENV local
process.env.TZ Asia/Tokyo
  console.error  # <===== User.tsの中で、エラーをコンソールで出すようにしているので表示されます。
    Unable to put item. Error users(2): {
      "name": "TransactionCanceledException",
      "$fault": "client",
      "$metadata": {
        "httpStatusCode": 400,
        "requestId": "4bf3f3bf-8542-4d2d-a353-2d1dd77d5bdb",
        "attempts": 1,
        "totalRetryDelay": 0
      },
      "CancellationReasons": [
        {
          "Code": "None"
        },
        {
          "Code": "ConditionalCheckFailed",
          "Message": "The conditional request failed"
        }
      ],
      "__type": "com.amazonaws.dynamodb.v20120810#TransactionCanceledException",
      "message": "Transaction cancelled, please refer cancellation reasons for specific reasons [None, ConditionalCheckFailed]"
    }

      146 |         } catch (err) {
      147 |             console.error("Unable to put item. Error users(2):", JSON.stringify(err, null, 2));
    > 148 |             throw new Error(`Unable to put item. Error users(2): ${err.message}`);
          |                 ^
      149 |         }
      150 |         return ret;
      151 |     };

      at User.createUser (src/model/User.ts:148:17)
      at Object.<anonymous> (test/User.test.ts:14:22)

 PASS  test/User.test.ts # <===== テストは正常に通過
  User登録に関するテスト
    ✓ 新規登録のテスト (41 ms)
    ✓ 二重登録エラーのテスト (18 ms)
  User取得に関するテスト
    ✓ 一覧取得テスト (6 ms)
  User更新に関するテスト
    ✓ 更新テスト (1011 ms)
  User削除に関するテスト
    ✓ 削除テスト (1013 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        2.381 s, estimated 3 s
Ran all test suites.

ローカルのDynamoDBのデータを作成して、更新、削除しているので、データ上は分からないですが、コンソール上では正常に動作していますね。

ソースを監視し、変更があると自動でテストを実行

以下コマンドを実行します。
すると、プログラム変更があったら自動でテストしてくれます。

jest --watchAll

以上

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