CDK のテストに Vitest を使う
『AWS CDK Advent Calendar 2024』7 日目の記事です。
はじめに
この記事では、CDK のテストに Vitest を使用する方法と、Vitest を使用したテストの書き方を紹介します。
Vitest とは?
Vitest とは Vite を利用した次世代の JavaScript テストフレームワークです
Vite というのは次世代フロントエンドツールですが、Vitest はフロントエンドに限らず、JavaScript / TypeScript で記述されたコードのテストを実行できます。
実際に私は CDK プロジェクトの Validation Test / Fine-grained assertions Test / Snapshot Test を全て Vitest で実行しています。
(各テストの詳細については以下の記事が参考になりました)
Vitest 導入手順
以下の手順で、CDK プロジェクトに Vitest を導入できます
1. インストールコマンドの実行
以下のコマンドを実行して、Vitest をインストールします
# npm
npm install --save-dev vitest
# yarn
yarn install -D vitest
# pnpm
pnpm install -D vitest
2. package.json の設定
続いて、ターミナル上で Vitest のコマンドが実行できるように、package.json の scripts
に test コマンドを追加します
(npx vitest
でも実行可能なので、scripts
への追加は必須ではありません)
{
"scripts": {
+ "test": "vitest",
}
}
3. Vitest の設定ファイル作成
続いて、プロジェクトのルートにvitest.config.ts
を作成します
(ファイル拡張子はts
以外にも指定できます)
以下に、vitest.config.ts
の例を紹介します
import { defineConfig } from "vitest/config";
export default defineConfig({
root: ".",
test: {
root: ".",
globals: true,
environment: "node",
include: ["**/test/**/*.{spec,test}.ts"]
},
});
4. tsconfig.json の更新
Vitest をグローバルに使用したい場合(global API を宣言無く呼び出せるようにしたい場合)、vitest の型情報を読み込ませるためにtsconfig.json
のcompilerOptions.types
を修正する必要があります。
修正内容は以下の通りです
{
"compilerOptions": {
// ...
+ "types": ["vitest/globals"]
},
}
ここまでで、Vitest のセットアップは完了です。
以降では、Vitest を使用した Validation Test / Fine-grained assertions Test / Snapshot Test の記述方法を紹介します。
Validation Test
バリデーションとは、条件分岐などを通して値の妥当性を検証する処理のことです。AWS CDK においても、Stack や Construct への入力である props のプロパティに対してバリデーション処理を実装することがあります。
引用: https://aws.amazon.com/jp/builders-flash/202411/learn-cdk-unit-test/
今回は、RDS のデータベース名をバリデーションすることを想定します。
Construct 側のコードは以下の通りです
interface RdsProps {
readonly databaseName: string;
// ...
}
export class Rds extends Construct {
constructor(scope: Construct, id: string, props: RdsProps) {
super(scope, id);
this.validateDatabaseName(props.databaseName);
this.validateUsername(props.username);
// ...
}
private validateDatabaseName(databaseName: string): void {
if (!this.isSnakeCase(databaseName)) {
throw new Error("データベース名はスネークケースで指定してください。");
}
}
private isSnakeCase(str: string): boolean {
const snakeCasePattern = /^[a-z]+(_[a-z]+)*$/;
return snakeCasePattern.test(str);
}
}
こちらのバリデーションに対するテストコードは以下の通りです
const createRdsInstance = (databaseName: string): Rds => {
const app = new App();
const stack = new Stack(app, "TestStack");
// ...
return new Rds(stack, "TestRds", {
// ...
databaseName,
});
};
describe("バリデーションテスト", () => {
test("データベース名がスネークケースでない場合はエラー", () => {
const databaseName = "invalid-name";
const errorMessage = "データベース名はスネークケースで指定してください。";
expect(() => createRdsInstance(databaseName)).toThrowError(errorMessage);
});
});
Fine-grained assertions Test
AWS CDK における Fine-grained assertions テストとは、生成された CloudFormation テンプレートの一部を取り出して、その部分に対してチェックを行うテストのことです。これにより、どのようなリソースが生成されるのかといった細かい構成要素に対するテストをすることができます。
引用: https://aws.amazon.com/jp/builders-flash/202411/learn-cdk-unit-test/
今回は、CloudFormation テンプレートの AWS::RDS::DBInstance
の Engine, EngineVersion, DBInstanceClass を検証することを想定します
テストコードは以下の通りです
const getTemplate = (): Template => {
const app = new App();
const stack = new BackCdkTrainingStack(app, "TestStack");
return Template.fromStack(stack);
};
describe("RDS Fine-grained assertions tests", () => {
const template = getTemplate();
test("RDSの設定が適切か", () => {
template.hasResourceProperties("AWS::RDS::DBInstance", {
Engine: "mysql",
EngineVersion: "8.0",
DBInstanceClass: "db.t4g.micro",
});
});
});
Snapshot Test
AWS CDK におけるスナップショットテストとは、CDK コードから合成される AWS CloudFormation テンプレートを出力し、以前のテスト実行時に生成したテンプレートの内容と比較してテンプレートの差分を検出するテストのことです。
引用: https://aws.amazon.com/jp/builders-flash/202411/learn-cdk-unit-test/
Snapshot Test を行う際に、CloudFormation テンプレートに付与されたハッシュ値をシリアライズする処理を行います。
これは、テストの実行ごとにハッシュ値が変わる場合(ECR の Image など)に対応するためです。
シリアライズを行う際には、Vitest の Custiom Serializer を使用します
import { SnapshotSerializer } from "vitest";
export default {
/**
* シリアライズは、文字列のみを対象とする
*/
test(val: unknown) {
return typeof val === "string";
},
/**
* ハッシュ値を置換する
*/
serialize(val: string) {
return val.replace(/[A-Fa-f0-9]{64}/, "hashed");
},
} satisfies SnapshotSerializer;
そして、こちらの Serializer を config ファイルに追加します
import { defineConfig } from "vitest/config";
export default defineConfig({
root: ".",
test: {
root: ".",
globals: true,
environment: "node",
+ snapshotSerializers: ["./plugins/vitestPlugin/customSerializer.ts"],
include: ["**/test/**/*.{spec,test}.ts"]
},
});
テストコードは以下の通りです。
const getTemplate = (): Template => {
const config = getConfigStackProps("dev");
const app = new App();
const stack = new BackCdkTrainingStack(app, "BackCdkTraining", config);
return Template.fromStack(stack);
};
test("Snapshot Tests", () => {
const template = getTemplate();
const json = template.toJSON();
expect(json).toMatchSnapshot();
});
Jest からの移行
cdk init
コマンドを使用して JavaScript / TypeScript の CDK プロジェクトを開始すると、自動で Jest というテストフレームワークがインストールされます。
そのため、テストフレームワークに Jest を採用している CDK ユーザーも少なくないのではと考えています。
もし、すでに Jest を使っている CDK ユーザーの中で、Vitest への移行を検討している方がおりましたら、以下のドキュメントを参照ください
(Vitest は Jest と互換性のある API が提供されているため、簡単に移行することができます)
まとめ
今回は、CDK プロジェクトに Vitest を導入する方法を紹介しました。
Vitest の導入を検討されている方、Vitest を使用したテストの書き方に悩んだいる方の役に立てば幸いです!
参考資料
Discussion