Snapshot testing your CDK stacks is the most effective way to catch unintended infrastructure changes before they hit production.
Let’s see it in action. Imagine you have a simple S3 bucket defined in your CDK code:
from aws_cdk import (
Stack,
aws_s3 as s3,
RemovalPolicy
)
from constructs import Construct
class MyS3Stack(Stack):
def __init__(self, scope: Construct, construct_id: str) -> None:
super().__init__(scope, construct_id)
s3.Bucket(self, "MyBucket",
versioned=True,
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True
)
When you synthesize this stack, CDK generates CloudFormation. We want to capture that CloudFormation output and compare it against future syntheses.
Here’s how you’d set up a basic snapshot test using jest (a popular JavaScript testing framework, often used with CDK):
First, install the necessary packages:
npm install --save-dev jest ts-jest @aws-cdk/assert
Then, create a test file, say s3-snapshot.test.ts:
import * as cdk from '@aws-cdk/core';
import { MyS3Stack } from '../lib/my-s3-stack'; // Assuming your stack is in lib/my-s3-stack.ts
test('S3 Stack Snapshot', () => {
const app = new cdk.App();
const stack = new MyS3Stack(app, 'MyTestS3Stack');
// We need to import the built-in CDK assertions
const { Template } = require('@aws-cdk/assertions');
// Synthesize the stack and get the CloudFormation template
const template = Template.fromStack(stack);
// Assert that the template matches the snapshot
expect(template).toMatchSnapshot();
});
Now, run the test:
npm test
The first time you run this, jest will create a __snapshots__ directory containing a .test.ts.snap file. This file holds the "snapshot" of your CloudFormation template. It will look something like this:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`S3 Stack Snapshot 1`] = `
Object {
"Resources": Object {
"MyTestS3StackMyBucket3F0A686A": Object {
"Type": "AWS::S3::Bucket",
"Properties": Object {
"VersioningConfiguration": Object {
"Status": "Enabled"
},
"PublicAccessBlockConfiguration": Object {
"BlockPublicAcls": true,
"BlockPublicPolicy": true,
"IgnorePublicAcls": true,
"RestrictPublicBuckets": true
}
}
}
},
"Outputs": Object {}
}
`;
This snapshot represents the exact CloudFormation that your CDK code generates for that S3 bucket.
The real power comes when you change your CDK code. Let’s say you accidentally remove versioned=True:
# ... inside MyS3Stack.__init__
s3.Bucket(self, "MyBucket",
# versioned=True, <-- accidentally commented out
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True
)
Running npm test again will now fail:
FAIL __tests__/s3-snapshot.test.ts
S3 Stack Snapshot
✕ S3 Stack Snapshot (142ms)
● S3 Stack Snapshot
expect(received).toMatchSnapshot()
Snapshot comparison failed:
- Expected
+ Received
@@ -6,8 +6,7 @@
"Resources": Object {
"MyTestS3StackMyBucket3F0A686A": Object {
"Type": "AWS::S3::Bucket",
"Properties": Object {
- "VersioningConfiguration": Object {
- "Status": "Enabled"
- },
"PublicAccessBlockConfiguration": Object {
"BlockPublicAcls": true,
"BlockPublicPolicy": true,
"IgnorePublicAcls": true,
"RestrictPublicBuckets": true
}
}
}
},
@@ -15,3 +14,4 @@
"Outputs": Object {}
}
+ at Object.<anonymous> (__tests__/s3-snapshot.test.ts:16:30)
The test output clearly shows what changed: the VersioningConfiguration property is now missing from the received CloudFormation. This immediately tells you that your code change removed bucket versioning.
To fix this, you have two options:
- Revert your code change: If the change was unintentional, simply uncomment
versioned=Trueand the test will pass again. - Update the snapshot: If the change was intentional and you want to allow it, run
npm test -- -u(orjest -u). This updates the snapshot file to reflect the new, intended state of your infrastructure.
This process gives you a concrete, auditable record of your infrastructure’s desired state, enforced by code.
The core mechanism at play is CDK’s synthesis process. When you call Template.fromStack(stack), you’re not deploying anything. Instead, CDK’s CloudFormation generator translates your construct tree into a JSON representation of an AWS CloudFormation template. This JSON is then serialized into a string and compared against the stored snapshot. The @aws-cdk/assertions library provides the Template object and its methods to make this synthesis and comparison straightforward within your testing framework.
One crucial detail most people overlook is how specific the snapshot is. It doesn’t just check for the existence of an S3 bucket; it checks for the exact properties defined in the CloudFormation. This includes things like PublicAccessBlockConfiguration, which CDK adds by default for security reasons even if you don’t explicitly configure it. If you later change the default security group for a lambda function, for instance, the snapshot will capture the new CIDR block for AllowAllOutbound or AllowAllInbound, ensuring you don’t accidentally open up access.
The next logical step after mastering snapshot testing is to integrate these tests into your CI/CD pipeline, ensuring that no unintended infrastructure drift occurs before deployment.