【AWS CDKテスト入門】Assertionテストのテストパターン ~Templateメソッド編~

# この記事について
前回[こちらの記事](https://qiita.com/pensuke628/items/8031748057a6ad6861f8)でSnapshotテストとAssertionテストの使い方について考えてみました。
今回からは、AWS CDK（Cloud Development Kit）のAssertionテストについて、具体的な書き方について深掘りしていきます。本記事では第一弾として、```Template```クラスのメソッドに焦点を当て、その使い方について紹介します。

# 対象読者
本記事は、以下のような読者を対象としています。
- インフラストラクチャのコードにテストを導入したいと考えている方
- CDKアプリケーションにテストを導入することを検討している方
- Assertionテストの書き方について知りたい方

# この記事では触れないこと
- AWS CDKの基礎的な使い方
- 特定のAWSリソースについての深い技術的解説

# 動作環境
この記事の内容は以下の環境で動作確認を行っています。
- Node.js 20.15.0
- TypeScript 5.5.2
- AWS CDK v2(2.150.0)

# テスト対象となるStack
```typescript:lib/cdk_test_study-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';

export class CdkTestStudyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const Bucket = new s3.Bucket(this, 'CdkTestStudyBucket1', {
      bucketName: 'hogehoge',
      enforceSSL: true,
      objectLockEnabled: true,
      versioned: true,
    });

    new s3.Bucket(this, 'CdkTestStudyBucket2', {
      objectLockEnabled: true,
      removalPolicy: cdk.RemovalPolicy.RETAIN
    });
  
    // S3のBucketNameをoutput
    new cdk.CfnOutput(this, 'S3BucketName', {
      value: Bucket.bucketName,
    });
  }
}
```


# リソースのプロパティの内容をチェックしたいとき
## hasResourceProperties
指定されたプロパティを持つリソースが存在することを確認することができます。

:::note warn
該当のプロパティを持つリソースが1つでもあればテストは成功します。作成されるAWSリソース全てが特定のプロパティを満たしているかを確認したい場合は、次に紹介する```allResourcesProperties```を使いましょう。
:::

```typescript
test("ObjectLockを有効にしているS3バケットが存在すること", () => {
  template.hasResourceProperties("AWS::S3::Bucket", {
    BucketName: "hogehoge"
  });
});
```

## allResourcesProperties
指定したリソース全てが特定のプロパティを持つことを確認することができます。
作成したS3全てがオブジェクトロック有効となっているかを確認したい場合などに有効です。

```typescript
test("全てのS3バケットでObjectLockが有効になっていること", () => {
  template.allResourcesProperties("AWS::S3::Bucket", {
    ObjectLockEnabled: true,
  });
});
```

# リソースのプロパティ以外の要素をチェックしたいとき
## hasResource
指定したタイプのリソースが存在することを確認することができます。
```hasResourceProperties```のとき同様、全てのAWSリソースに対してテストしたい場合は```allResources```を使いましょう。

```typescript
test("削除ポリシーがRetainでS3バケットが存在すること", () => {
  template.hasResource("AWS::S3::Bucket", {
    DeletionPolicy: "Retain",
  });
});
```

## allResources
指定したタイプのリソースが存在することを確認することができます。
hasResourcePropertiesのとき同様、全てのAWSリソースに対してテストしたい場合は```allResources```を使いましょう。

```typescript
test("全てのS3バケットの削除ポリシーがRetainであること", () => {
  template.allResources("AWS::S3::Bucket", {
    DeletionPolicy: "Retain",
  });
});
```

# リソースの個数をチェックしたいとき
## resourceCountIs
リソースが、指定された数だけ存在することを確認することができます。

```typescript
test("作成されるS3バケットが2つであること", () => {
  template.resourceCountIs("AWS::S3::Bucket", 2);
});
```

## resourcePropertiesCountIs
指定したプロパティを持つリソースが、指定された数だけ存在することを確認することができます。

```typescript
test("ObjectLockを有効にしているS3バケットが2つ存在すること", () => {
  template.resourcePropertiesCountIs("AWS::S3::Bucket", {
    ObjectLockEnabled: true,
  }, 2);
});
```

# Templateから特定のリソースに関する情報を取得したいとき
CloudFormationTempalteから特定のリソースに関する情報を取得したい場合は、以下の```find〇〇```メソッドを使用します。例として```findResources```を紹介していますが、Templateのセクションに合わせて、```find〇〇```メソッドを同じように使うことができます。

## findResources
TemplateのResourcesセクションから、条件に該当する特定のリソースタイプに関する部分を抽出することができます。
例えば、S3 Bucketに関する部分を出力したい場合は以下のように記述します。

```typescript
const s3 = template.findResources("AWS::S3::Bucket", {});
```

```
{
  CdkTestStudyBucket13535E829: {
    Type: 'AWS::S3::Bucket',
    Properties: {
      BucketName: 'hogehoge',
      ObjectLockEnabled: true,
      VersioningConfiguration: [Object]
    },
    UpdateReplacePolicy: 'Retain',
    DeletionPolicy: 'Retain'
  },
  CdkTestStudyBucket2D584685D: {
    Type: 'AWS::S3::Bucket',
    Properties: { ObjectLockEnabled: true },
    UpdateReplacePolicy: 'Retain',
    DeletionPolicy: 'Retain'
  }
}
```
リソースタイプのみ指定する場合は該当リソースに関する部分が全て抽出されるため、Propertiesを指定することでリソースを限定することもできます。

```typescript
const s3 = template.findResources("AWS::S3::Bucket", {
  Properties: {
    BucketName: "hogehoge",
  },
});
```

```
{
  CdkTestStudyBucket13535E829: {
    Type: 'AWS::S3::Bucket',
    Properties: {
      BucketName: 'hogehoge',
      ObjectLockEnabled: true,
      VersioningConfiguration: [Object]
    },
    UpdateReplacePolicy: 'Retain',
    DeletionPolicy: 'Retain'
  }
}
```

# Templateのセクションに特定の値が含まれているか確認したいとき
CloudFormationTemplateから各セクションに特定の値が含まれているか確認したい場合は、以下の```has〇〇```メソッドを使用します。例として```hasOutputs```を紹介していますが、Templateのセクションに合わせて、```has〇〇```メソッドを同じように使うことができます。

## hasOutputs
TemplateのOutputsセクションに、条件に該当する記述が含まれている確認することができます。例えば、S3 BucketをOutputsに含めているか確認する場合は以下のように記述します。

```typescript
test("S3のBucketNameをOutputしていること", () => {
  const s3 = template.findResources("AWS::S3::Bucket", {
    Properties: {
      BucketName: "hogehoge",
    },
  });

  template.hasOutput('S3BucketName', {
    Value: { 
      Ref: Object.keys(s3)[0]
    }
  });
});
```

# まとめ
本記事ではアサーションテストで使用するメソッドのうち、Templateクラスのメソッドについて紹介しました。
```hasResourceProperties```や```resourceCountIs```などのメソッドは特に利用頻度も高く、Assertionテストを作成する上で知っておくべきメソッドだと感じました。
次回はより詳細なAssertionテストを作成するために欠かせない、Matchクラスのメソッドについて深掘りしたいと思います。

# 参考
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.assertions.Template.html