When creating infrastructure on AWS using IaC (Infrastructure as Code) tools, knowing if the CloudFormation Template generated by the code has the expected definition is often challenging. There is also a need to ensure that the eventual changes introduce no unintended breaks to your infrastructure code.
In this article, I will explore why CDK (Cloud Development Kit) is a game-changer for testing IaC and how to leverage the CDK Fine-Grained Assertions approach in TypeScript to check the integrity of your Lambda functions definition before deploying your infrastructure.
Introduction
One of the biggest challenges of using IaC tools such as Serverless Framework, AWS SAM (Serverless Application Model), or Terraform is to make sure the output is correct and according to the standards defined by you or your organization, because these tools are YAML or JSON based.
This is especially true for Lambda functions, where you want to make sure that your infrastructure code is generating a Lambda function that has, for instance, the desired timeout time, memory size, and the required IAM permissions to serve its purpose.
Gladly, when using CDK as your IaC tool, it is possible to create assertions and let it fail in the CI if some configuration is deviated due to code change, for example. This approach is also known as Regression Tests.
CDK: A game changer for testing IaC
When AWS introduced CDK in 2019, the possibility of writing infrastructure using familiar programming languages such as TypeScript and Java sounded nice. However, I have to be honest - it did not catch my attention at first.
From https://docs.aws.amazon.com/cdk/v2/guide/home.html
I've had quite some experience with Serverless Framework and AWS SAM, and I was more in favor of using these tools because their use of YAML files provides a more explicit and declarative way to define infrastructure - less abstraction - compared to CDK.
It was only in November 2021 that I changed my mind: The CDK assertions library was announced and that was, for me, a true game changer: being able to write infrastructure in TypeScript was already great, but writing fine-grained tests towards infrastructure code really caught my eye.
Does that mean that CDK is better?
The short and cliche answer is: It depends. I personally still like YAML-based IaC tools for specific use cases, for instance:
- Simple and/or small projects, when you need a more descriptive infrastructure definition with lower abstraction.
- When you work in a team that is already familiar with YAML and has less knowledge of programming languages, reducing the learning curve.
Nowadays, for new projects that I know it is going to grow, and demand strict testing, I'd rather start with CDK especially due to the possibility of testing these resources before I even have to deploy them - and having full control of what is been tested.
Let's get down to business and explore how to implement regression tests with CDK fine-grained assertions.
CDK Fine-Grained Assertions
Assuming you have a code base with a CDK app, it is very easy to start writing assertions against the defined resources.
To demonstrate implementing regression tests in the CDK code, I've written a simple CDK app that creates a Lambda function, with a CloudWatch Log Group and an IAM Role with a defined IAM Policy, using TypeScript, as it's the language I am most familiar with.
I've created a test folder on the root of the project and a test file to start implementing the tests.
test/cdk-fine-grained-tests.test.ts
import { Template } from 'aws-cdk-lib/assertions';
import { App } from 'aws-cdk-lib';
import { CdkFineGrainedTestsStack } from '../lib/cdk-fine-grained-tests-stack';
describe('MyFunction Fine-Grained Tests', () => {
const app = new App();
const stack = new CdkFineGrainedTestsStack(app, 'CdkFineGrainedTestsStack', {});
const template = Template.fromStack(stack);
});
Using the Template
class from the CDK assertions library, I've created a "copy" of the CloudFormation template generated by the CDK stack.
After defining the main structure of the test file, we can start writing the assertions against the CloudFormatin template, to check the integrity of the configuration we want to include in the regression test. For the Lambda Function, it is important to check if the memory size does not get too big for its purpose, possibly incurring cost increases. The same goes for duration, architecture, and runtime.
it('should have created a lambda function with default configuration', () => {
template.hasResourceProperties('AWS::Lambda::Function', {
FunctionName: 'my-function',
Handler: 'index.handler',
Runtime: 'nodejs22.x',
Architectures: ['arm64'],
Timeout: 30,
MemorySize: 128,
});
});
You can also check if the Lambda function's CloudWatch Log Group has the desired retention policy:
it('should have created a log group with the correct retention policy', () => {
template.hasResourceProperties('AWS::Logs::LogGroup', {
LogGroupName: '/aws/lambda/my-log-group',
RetentionInDays: 7,
});
});
And finally, ensuring the Lambda has the required IAM permissions to execute the actions:
it('should have created a iam service role with the lambda basic execution role', () => {
template.hasResourceProperties('AWS::IAM::Role', {
ManagedPolicyArns: [
{
'Fn::Join': [
'',
[
'arn:',
{ Ref: 'AWS::Partition' },
':iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
],
],
},
],
});
});
it('should have created a iam policy with the correct permissions', () => {
template.hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 's3:GetObject',
Effect: 'Allow',
Resource: 'arn:aws:s3:::my-bucket/*',
},
],
},
});
});
You eventually would like to ensure that these properties remain untouched on new code changes unless this change is deliberated. If that is the case, the assertion also would need to be updated, otherwise the regression test will fail - and that is what we want.
The full example can be found here.
Executing the tests on the CI
After implementing the assertions in your code base, it is wise to execute the tests when you push changes to your branch.
For the example above, I've implemented a workflow to execute the tests in GitHub Actions on every push to the main
branch:
name: Build and run tests
on:
push:
branches:
- "main"
jobs:
build-and-test:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
id: install-dependencies
run: |
npm ci
- name: Run CDK fine-grained tests
id: cdk-fine-grained-tests
run: |
npm run test
This way you ensure that this step is executed before deployment, allowing early feedback about how the changes impacted your infrastructure.
Beyond Lambda Functions
Although this article is focused on Lambda Functions, CDK Fine-Grained Assertions can be used for any piece of infrastructure you want to implement regression tests.
Let's say that your CDK code creates an API Gateway. You could implement regression tests against, for example, a definition of a Gateway Response for the BAD_REQUEST
exception as follows:
it('should have created a gateway response for bad request', () => {
template.hasResourceProperties(
'AWS::ApiGateway::GatewayResponse',
{
ResponseType: 'BAD_REQUEST_BODY',
StatusCode: '400',
ResponseTemplates: {
'application/json':
'{
\n "message": "$context.error.validationErrorString"\n
}',
},
},
);
});
As long as you know what resources your infrastructure has and what you would like to test, you have a lot of possibilities. That is because the assertions are made against the CloudFormation template generated by your CDK code.
Please refer to the CDK assertions library.
Conclusion
AWS CDK with Fine-Grained Assertions enables precise testing of infrastructure by validating the CloudFormation templates it generates. This approach ensures that resources, such as Lambda functions, adhere to specific configurations like memory size, timeout, and IAM permissions.
By integrating these tests into a CI/CD pipeline, you can catch unintended changes early and maintain confidence in your infrastructure code.
While YAML-based tools may suit simpler projects, CDK’s combination of programming flexibility and fine-grained testing makes it a nice choice for scalable and test-driven infrastructure development.
What are your thoughts? Let me know in the comments below, I would love to hear what you have to say!
Top comments (0)