前記事(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に対して、以下を順番に実施します。
- User登録に関するテスト
- User取得に関するテスト
- User更新に関するテスト
- 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
以上