0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CDKのテストを書いてみた

Posted at

AWS CDKを利用してインフラ環境を作成したのでテストまで記述してみました。

CDKのテストは公式からモジュールが提供されているのでこれを利用してテストを書きます。
AWS CDKのテストには2つのカテゴリがあります。

  • アサーションテスト 生成されたCloudFormationテンプレートに対して、リソースが特定のプロパティを持つかをテストします。改修やリファクタ時の回帰テストやテスト駆動開発にて新機能を開発する場合に役立ちます。

  • スナップショットテスト CDKスタックから合成したCloudFormationテンプレートに対して、以前保存したテンプレートとの差分を比較します。リファクタされたコードが元のコードと全く同じように動作することを確認できます。

使用しているリソースは主にECR, AppRunner, DynamoDBとなります。AppRunnerでフロントとバックエンドの環境を動かしています。

アサーションテスト

スタックのインスタンスを作成してCloudFormationのテンプレートを生成するところから始まります。
console.log(template.findResources('AWS::ECR::Repository')) でマッチするリソースのセットを取得できます。

ECRのテスト

特定のリポジトリ名のプロパティをもつECRの確認

  test('ECR repository stack', () => {
    const app = new cdk.App();
    const stack = new Cdk.ECRStack(app, 'ECRStack', { appName: 'app-prod' });
    const template = Template.fromStack(stack);
    
    template.hasResourceProperties('AWS::ECR::Repository',{
      RepositoryName: "app-prod-front",
    });
    template.hasResourceProperties('AWS::ECR::Repository',{
      RepositoryName: "app-prod-backend",
    });
  })

DynamoDBのテスト

hasResourceはリソース定義全体をアサートしたい時に使えます。

  test('Infra stack', () => {
    const app = new cdk.App();
    const stack = new InfraStack(app, 'ApplicationInfra');
    const template = Template.fromStack(stack);

    template.resourceCountIs('AWS::DynamoDB::Table', 1);
    template.hasResource('AWS::DynamoDB::Table', {
      Properties: {
        TableName: 'Book',
        BillingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      },
      DeletionPolicy: "Delete",
    })
  })

AppRunnerのテスト

AppRunnerはプロパティを持っているかのテストと環境変数がそれぞれセットされているかを確認するテストを記述しました。
RuntimeEnvironmentSecretsをcaputureして環境変数のkeyが存在するかのテストを行うようにしてみました。

  test('AppRunner stack', () => {
    const app = new cdk.App();
    const stack = new AppRunnerStack(app, 'ApplicationStack', appEnvProp);
    const template = Template.fromStack(stack);

    template.resourceCountIs('AWS::AppRunner::Service', 2);
    template.hasResourceProperties('AWS::AppRunner::Service', {
        ServiceName: `${appName}-front`,
        SourceConfiguration: {
          AutoDeploymentsEnabled: true,
          ImageRepository: {
            ImageRepositoryType: 'ECR',
            ImageConfiguration: {
              Port: "3000",
              RuntimeEnvironmentSecrets: Match.anyValue(),
            }
          },
        },
    });

    template.hasResourceProperties('AWS::AppRunner::Service', {
        ServiceName: `${appName}-backend`,
        SourceConfiguration: {
          AutoDeploymentsEnabled: true,
          ImageRepository: {
            ImageRepositoryType: 'ECR',
            ImageConfiguration: {
              Port: "8080",
              RuntimeEnvironmentSecrets: Match.anyValue(),
            }
          },
        },
    });
  })
  test('Is Env key setting in FrontEnd AppRunnerStack', () => {
    const app = new cdk.App();
    const stack = new AppRunnerStack(app, 'ApplicationStack', appEnvProp);
    const template = Template.fromStack(stack);
    const frontAppRunnerCapture = new Capture()
    template.hasResourceProperties('AWS::AppRunner::Service', {
        ServiceName: `${appName}-front`,
        SourceConfiguration: {
          ImageRepository: {
            ImageConfiguration: {
              RuntimeEnvironmentSecrets: frontAppRunnerCapture,
            }
          },
        },
    });
    expect(frontAppRunnerCapture._captured[0][0]).toHaveProperty("Name", "HOSTNAME")
  })
  test('Is Env key setting in Backend AppRunnerStack', () => {
    const app = new cdk.App();
    const stack = new AppRunnerStack(app, 'ApplicationStack', appEnvProp);
    const template = Template.fromStack(stack);
    const backendAppRunnerCapture = new Capture()

    template.hasResourceProperties('AWS::AppRunner::Service', {
      ServiceName: `${appName}-backend`,
      SourceConfiguration: {
        ImageRepository: {
          ImageConfiguration: {
            RuntimeEnvironmentSecrets: backendAppRunnerCapture,
          }
        },
      },
    });
    const backEnvironmentSecrets = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "DYNAMODB_KEY", "DYNAMODB_SECRET", "DYNAMODB_CONNECTION"];
    backendAppRunnerCapture._captured[0].forEach((value: any) => {
      expect(backEnvironmentSecrets.includes(value["Name"])).toBeTruthy()
    })
  })

スナップショットテスト

各スタックのスナップショットの確認を行います

describe("snapshot test", () => {
  test("matches the ecr snapshot", () => {
    const app = new cdk.App();
    const stack = new ECRStack(app, "EcrStack");

    const template = Template.fromStack(stack);
    expect(template.toJSON()).toMatchSnapshot();
  });
  test("matches the infra snapshot", () => {
    const app = new cdk.App();
    const stack = new InfraStack(app, "ApplicationInfra");

    const template = Template.fromStack(stack);
    expect(template.toJSON()).toMatchSnapshot();
  });
  test("matches the AppRunner snapshot", () => {
    const app = new cdk.App();
    const stack = new AppRunnerStack(app, "ApplicationStack", {
      appName,
      envName,
      frontendRepositoryName,
      backendRepositoryName
    });

    const template = Template.fromStack(stack);
    expect(template.toJSON()).toMatchSnapshot();
  });
});

初回実行時に_snapshots_ディレクトリにスナップショットが作成されました。
スクリーンショット 2024-06-11 14.01.28.png

コードに変更が加わると前回のスナップショットと異なるのでテストはfailedになってしまいます。
npm test -- -uをすることでスナップショットを更新できます。

テストを実行するとアサーションテストとスナップショットテストそれぞれの結果を確認することができます。
スクリーンショット 2024-06-11 14.01.58.png

参考

Testing constructs

感想

意外と簡単にできた気がします。プロパティの確認などどこまで書くべきなのかは疑問
AWS CDKのテストはどこまで書くべきか?

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?