The AWS Cloud Development Kit (CDK) allows us to define our cloud infrastructure using code. Instead of clicking through the web UI, or AWS console as it is called, we use code. Code that can be checked into version control for collaboration, annotated with documentation and certainly for testing.
The following post focusses on the testing aspect.
If we choose TypeScript we can initialise a CDK project with the following command after having installed
cdk init app --language typescript
The project ends up with a generated empty stack. As the comment states we have not defined anything yet.
As an addition an initial test case has been generated in the
test directory of our project. It makes sure to test the fact our stack is empty. As defined above. Having the test directory and an initial test already tells us the CDK code can be tested and the intention of the framework authors to have tests.
We can run the tests via
npm run test and get the following results:
How does this work? We have an assertion checking whether our template has resources. The template used for comparison is our Cloud Formation template. It is the output of
Without changing the generated
CdkTestsStack, which has no resources defined yet, it looks as follows:
Resources only have the
CDKMetadata entry. This is about to change.
Let’s create a private ECR repository for our docker image. The image will later be used to run our application with AWS Fargate. The image is uploaded to the ECR repository and AWS Fargate will pick it up and run it.
npm i @aws-cdk/aws-ecr to get the required dependencies and extend the stack.
cdk synth again provides the following output:
Now our repository is present in the resources. Our first resource! On to writing the test.
We assert our stack has the
AWS::ECR::Repository resource with the
RepositoryName property value
test. The test passes.
Time to set up our
ApplicationLoadBalancedFargateService. The construct is part of the
@aws-cdk/aws-ecs-patterns. We reference our ECR repository in the
taskImageOptions. Making sure the
latest image is picked up by Fargate.
Aside from the
image property we did not provide other configuration. Thus the default values are used. We could write tests to be sure which values are used.
This both increases visibility of which values are used and makes sure we would learn about changes to the construct even when updating
aws-ecs-patterns to a newer version. It allows us to learn if, for example, the
DesiredCount default would ever change to two instances.
The next test asserts some of the default values.
We should only do this with properties that are important to us.
The test passes. Let’s try to test if the
ApplicationLoadBalancedFargateService is using the latest image in our repository.
Here we can use
haveResourceLike instead of
haveResource. Else we would need to specify the values for
PortMappings too to allow for a proper comparison of
Using the CloudFormation JSON syntax makes the test pretty much unreadable. It has to be copied from the
cdk synth output and pasted into the test code. It also gives us a glimpse of what writing CloudFormation templates would look like. In the future this cold be moved into a helper function.
Say we are building a Spring Boot application. Thus the url to conduct the health check of our services via load balancer is
/actuator/health. We need a way to configure it. It is a possible property in our
We write the following test
The new test fails. We can fix it by extending our stack and configuring the health check.
As a result
cdk synth provides the new Cloud Formation template
And all of our four tests pass
Sure, the health check will only work if we provide the default Spring Boot port of
8080 to the
taskImageOptions. But that is for another time.
There are plenty of projects around that don’t test their CDK code. They might be fully productive without it. The maintainers of the CDK project have tested the constructs to make sure they work. Thus we won’t have to. Still, there are some benefits to having them under test.
The tests can act as a sanity check. Are the constructs doing what I’m expecting them to do? As an early warning system if someone inadvertently removes something important. What if someone removes some critical configuration deemed unnecessary from their perspective? They can be used as documentation and specification for future developers or our own future selves. And of course as part of the test driven development workflow. We might not get as much design feedback on the constructs used from libraries but it will help to structure the way in which we work. Step by step.
The post has been cross-posted to Medium