Verdict
Yes
Yes, unit test CDK code minimally: one test per stack that asserts its tables and buckets keep their logical IDs; leave wiring errors to the staging deploy, and do not assert every declared property.
Why
- Blast radiususers
- Change frequencyregularly
- Detectabilitysame-day
- Reversibilitywith-effort
- Test costmoderate
Yes, unit test CDK code minimally: one test per stack that pins the logical IDs of the tables and buckets that hold data. The typical case is a TypeScript CDK app with Lambda functions, an API, a DynamoDB table and a bucket, deployed to staging before production. Blast radius is users, because a broken stack takes a feature down, and Change frequency is regularly, because the stack changes with features. Detectability is same-day, because a missing grant fails the staging deploy or its integration tests, and Reversibility is with-effort: another pipeline run plus follow-up on failed requests. Test cost is moderate, because Template.fromStack bundles every Lambda asset and needs updates when the stack changes, so rule R12 gives Test minimally.
| When | Decision | Why |
|---|---|---|
| Your CDK construct branches on the stage, so some alarms exist only in the production template | Test: synthesize the stack with each stage's props and assert the settings that differ | Detectability moves to eventually, because staging deploys the other branch |
| An S3 bucket defined in CDK holds personal data, such as identity documents | Test mandatory: assert Block Public Access, encryption and no wildcard principal in the bucket policy | Blast radius rises to safety-or-legal and Detectability to never, because a public bucket serves files with no error |
| Other teams install your CDK constructs as a package | Test: one fine-grained assertion for each prop combination that changes the template | Detectability moves to eventually, because a break shows in stacks the author never deploys |
| The CDK stack holds no data, and each pull request deploys it to its own stage where integration tests call every route | Do not unit test the template; keep the deploy per pull request and its integration tests | Detectability rises to immediately and Reversibility falls to trivial, because a redeploy leaves nothing behind |
| Tables or databases in the stack declare deletion protection or point-in-time recovery | Test: assert those settings in the synthesized template of each stack | Detectability moves to eventually, because a dropped setting deploys without an error and shows only when the data is needed |
| The failure you fear is a console edit in the AWS account, not a change to the CDK template | Test it differently: turn on the AWS Config drift detection rule and alert when a stack drifts | Test cost rises to prohibitive, because a template test cannot see the account, and Detectability moves to eventually |
What breaks if you don't test
Most CDK mistakes fail loudly in staging. The quiet one is a refactor that moves a table to a new place in the construct tree. The CDK builds the logical ID from that path, and when the ID changes, CloudFormation creates a new resource and deletes the old one. With the default RETAIN policy the old table survives outside the stack, the functions switch to an empty table, and customers report missing data.
What you lose if you over-test
Fine-grained assertions on every property repeat the stack line by line, so each change is made twice, the cost Yan Cui describes for declarative stacks. A snapshot of the whole template fails on CDK upgrades, as the CDK testing guide warns, and on every handler edit, because the template carries each Lambda asset's hash.
How to test
Use the assertions module with Jest or pytest:
- One test per stack, synthesized with production props, that asserts the logical ID and Retain policy of each table, bucket and database, as the CDK guide advises.
- A regression test when a bug reaches the stack.
Staging cannot catch a changed ID: the deploy replaces the staging table too, and tests that seed their own data pass.
Procedure and references
When the answer changes
- Your constructs branch on the stage.
- A bucket or policy guards personal data.
- Other teams install your constructs.
Real incident + Code example
The orders table that moved
On an order service I worked on, a developer moved the DynamoDB orders table into a new Storage construct, and its logical ID changed. Staging integration tests passed, because they create their own orders. In production CloudFormation created an empty table, kept the old one outside the stack, and gave the functions the new name. Customers saw empty order histories for 40 minutes. Recovery took most of a day: cdk import brought the old table back, and a script copied the orders written in between. The test we added fails on that refactor:
import { App } from "aws-cdk-lib";
import { Template } from "aws-cdk-lib/assertions";
import { OrdersStack } from "../lib/orders-stack";
import { prodProps } from "../lib/stages";
test("stateful resources keep their logical IDs", () => {
const stack = new OrdersStack(new App(), "Orders", prodProps);
const template = Template.fromStack(stack);
// A new ID here makes CloudFormation create an empty replacement.
expect(Object.keys(template.findResources("AWS::DynamoDB::Table")))
.toEqual(["OrdersTable8A3C5F21"]);
expect(Object.keys(template.findResources("AWS::S3::Bucket")))
.toEqual(["AttachmentsBucket4F1D2B90"]);
template.hasResource("AWS::DynamoDB::Table", {
DeletionPolicy: "Retain",
UpdateReplacePolicy: "Retain",
});
});
Sources
- docs.aws.amazon.com/cdk/v2/guide/testing.html
- docs.aws.amazon.com/cdk/v2/guide/best-practices.html#best-practices-constructs-logicalid
- docs.aws.amazon.com/cdk/v2/guide/identifiers.html#identifiers-logical-ids
- theburningmonk.com/2023/06/no-you-dont-need-to-test-every-line-of-your-cdk-application
- docs.aws.amazon.com/config/latest/developerguide/cloudformation-stack-drift-detection-check.html
Related questions
FAQ
- Do you need to unit test CDK against the synthesized CloudFormation template?
Yes, test the synthesized CDK template for what a deploy does not reveal: logical IDs of stateful resources, settings that differ by stage, and protective settings on data.
- Are CDK snapshot tests worth it?
No, a snapshot of the whole CDK template does not pay off as a permanent test, because it fails on CDK upgrades and on every Lambda code edit. Keep one only for a refactor that should leave the template unchanged.
- How do I stop a CDK refactor from replacing a DynamoDB table?
Keep the table's construct path, or, when you wrap the table in a new construct, give that construct the table's old ID and the table the ID
Default. Pin the table's logical ID in a unit test so that a changed path fails CI.- Should I test custom CDK constructs?
Yes, test a custom CDK construct that branches on its props or that other teams install, with one assertion for each prop combination that changes the template. A construct used once in your own stack needs only the stack's logical ID test.