AWS CDK v2のスナップショットテストでアセットを無視する方法

こんにちは。サービスグループの武田です。AWS CDK v2のスナップショットテストのアセットの差分を、Snapshot Serializerを利用して無視してみましょう。
2022.06.30

以前、AWS CDKのスナップショットテストで、ハッシュを置換しアセットを無視する方法を紹介しました。

この方法はAWS CDK v1のお話でして、v2では使えません。というわけで、このエントリではCDK v2でアセットを無視する方法を紹介します。

忙しい人のための結論

test/snapshot-plugin.tsを次のように修正すればCDK v2で動作します(Snapshot Serializerを利用している場合)。

test/snapshot-plugin.ts

module.exports = {
  test: (val: unknown) => typeof val === 'string',
  serialize: (val: string) => {
    return `"${val.replace(/([A-Fa-f0-9]{64}.zip)/, 'HASH-REPLACED.zip')}"`;
  },
};

普通にスナップショットテストをしてみる

v1のエントリをv2でなぞり直します。

まずはスナップショットの差分が発生することを確認しましょう。プロジェクトを作成していきます。

$ mkdir cdk-ignore-test-assets && cd $_
$ npx cdk@2 init --language typescript
$ npm install @aws-cdk/aws-lambda-python-alpha

次にLambda関数のファイルを作成します。

lib/lambda/hello/index.py

def handler(event, context):
    return "success"

スタックファイルを更新します。

lib/cdk-ignore-test-assets-stack.ts

import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import { PythonFunction } from "@aws-cdk/aws-lambda-python-alpha";
import { Runtime } from "aws-cdk-lib/aws-lambda";

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

    new PythonFunction(this, "LambdaFunction", {
      entry: "lib/lambda/hello",
      runtime: Runtime.PYTHON_3_9,
    });
  }
}

テストファイルも更新します。

test/cdk-ignore-test-assets.test.ts

import { App } from "aws-cdk-lib";
import { Template } from "aws-cdk-lib/assertions";
import { CdkIgnoreTestAssetsStack } from "../lib/cdk-ignore-test-assets-stack";

test("Snapshot Test", () => {
  const app = new App();
  const stack = new CdkIgnoreTestAssetsStack(app, "cdkIgnoreTestAssetsStack");

  expect(Template.fromStack(stack).toJSON()).toMatchSnapshot();
});

この状態で一度テストを実行します。

$ npm run test

取得したスナップショットを抜粋します。次のような内容が確認できます。

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Snapshot Test 1`] = `
Object {
  "Parameters": Object {
    "BootstrapVersion": Object {
      "Default": "/cdk-bootstrap/hnb659fds/version",
      "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]",
      "Type": "AWS::SSM::Parameter::Value<String>",
    },
  },
  "Resources": Object {
    "LambdaFunctionBF21E41F": Object {
      "DependsOn": Array [
        "LambdaFunctionServiceRoleC555A460",
      ],
      "Properties": Object {
        "Code": Object {
          "S3Bucket": Object {
            "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}",
          },
          "S3Key": "2b75c1affb2060ce201327fd694b7f436c08fdeb0e229fe637c9dd63d5c7798a.zip",
        },
        "Handler": "index.handler",
        "Role": Object {
          "Fn::GetAtt": Array [
            "LambdaFunctionServiceRoleC555A460",
            "Arn",
          ],
        },
        "Runtime": "python3.9",
      },
      "Type": "AWS::Lambda::Function",
    },

S3Keyの部分にアセット名が入っています。Lambdaのコードを修正することで、テストが失敗することも確認してみましょう。

diff --git a/lib/lambda/hello/index.py b/lib/lambda/hello/index.py
index a2c6b78..f87f532 100644
--- a/lib/lambda/hello/index.py
+++ b/lib/lambda/hello/index.py
@@ -1,2 +1,2 @@
 def handler(event, context):
-    return "success"
+    return "success!!"

テストを実行します。

$ npm run test

> cdk-ignore-test-assets@0.1.0 test
> jest


 RUNS  test/cdk-ignore-test-assets.test.ts

 FAIL  test/cdk-ignore-test-assets.test.ts (16.294 s)
  ✕ Snapshot Test (5479 ms)

  ● Snapshot Test

    expect(received).toMatchSnapshot()

    Snapshot name: `Snapshot Test 1`

    - Snapshot  - 1
    + Received  + 1

    @@ -14,11 +14,11 @@
            "Properties": Object {
              "Code": Object {
                "S3Bucket": Object {
                  "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
                },
    -           "S3Key": "2b75c1affb2060ce201327fd694b7f436c08fdeb0e229fe637c9dd63d5c7798a.zip",
    +           "S3Key": "50a81b2cd570ab34a55020d7f74bc683b717c74b973f810fd59b4707ecb61f1f.zip",
              },
              "Handler": "index.handler",
              "Role": Object {
                "Fn::GetAtt": Array [
                  "LambdaFunctionServiceRoleC555A460",

       7 |   const stack = new CdkIgnoreTestAssetsStack(app, "cdkIgnoreTestAssetsStack");
       8 |
    >  9 |   expect(Template.fromStack(stack).toJSON()).toMatchSnapshot();
         |                                              ^
      10 | });
      11 |

      at Object.<anonymous> (test/cdk-ignore-test-assets.test.ts:9:46)

 › 1 snapshot failed.
Snapshot Summary
 › 1 snapshot failed from 1 test suite. Inspect your code changes or run `npm test -- -u` to update them.

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   1 failed, 1 total
Time:        16.47 s, estimated 137 s
Ran all test suites.

失敗しましたね。

アセットを無視するスナップショットテストをやってみる

それではアセットで差分が出ることは確認できましたので、このエントリの目的である、それを無視するスナップショットテストをやってみましょう。

まずはモジュールを定義します。

test/snapshot-plugin.ts

module.exports = {
  test: (val: unknown) => typeof val === 'string',
  serialize: (val: string) => {
    return `"${val.replace(/([A-Fa-f0-9]{64}.zip)/, 'HASH-REPLACED.zip')}"`;
  },
};

このモジュールを使用するように、設定ファイルを編集します。

jest.config.js

module.exports = {
  roots: ['<rootDir>/test'],
  testMatch: ['**/*.test.ts'],
  transform: {
    '^.+\\.tsx?$': 'ts-jest'
  },
  snapshotSerializers: ['<rootDir>/test/snapshot-plugin.ts']
};

一度スナップショットは削除して、テストを実行してみます。

$ rm test/__snapshots__/cdk-ignore-test-assets.test.ts.snap
$ npm run test

スナップショットを確認してみると、HASH-REPLACED.zipに置き換わっていることが確認できます。

      "Properties": Object {
        "Code": Object {
          "S3Bucket": Object {
            "Fn::Sub": "cdk-hnb659fds-assets-\${AWS::AccountId}-\${AWS::Region}",
          },
          "S3Key": "HASH-REPLACED.zip",

この状態で、先ほどと同じようにLambda関数のコードを書き換えて、再度テストしてみましょう。

$ npm run test

> cdk-ignore-test-assets@0.1.0 test
> jest


 RUNS  test/cdk-ignore-test-assets.test.ts
 PASS  test/cdk-ignore-test-assets.test.ts (12.168 s)
  ✓ Snapshot Test (2237 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        12.389 s, estimated 15 s
Ran all test suites.

アセットが変わってもテストをパスしました!

まとめ

CDK v1とv2では出力されるデータに差異があるため、置換する処理も合わせて変更する必要がありました。CDK v2でもスナップショットテストを導入しようとしている方の参考になれば幸いです。