Unit Test of IaC CDK GO

CDK Code generates CloudFormation

The file dsl.go contains CDK code, which creates the CloudFormation template.

Let’s look at the Lambda Function as an example:

myHandler := awslambda.NewFunction(stack, aws.String("myHandler"), 
	&awslambda.FunctionProps{
		Description:                  aws.String("dsl - dynamodb s3 lambda"),
		FunctionName:                 aws.String("logincomingobject"),
		LogRetention:                 logs.RetentionDays_THREE_MONTHS,
		MemorySize:                   aws.Float64(1024),
		Timeout:                      awscdk.Duration_Seconds(aws.Float64(10)),
		Code: awslambda.Code_FromAsset(&lambdaPath, &awss3assets.AssetOptions{}),
		Handler: aws.String("main"),
		Runtime: awslambda.Runtime_GO_1_X(),
	})

When you call cdk synth (just generate) or cdk diff (see differences to deployed stack) or cdk deploy (deploy stack), inside the directory cdk.out the template is generated in the file dsl.template.json.

The naming schema is ${stackname}.template.json. So if you rename the stack, some orphaned files will be created.

Delete the cdk.out directory from time to time, it takes a lot of space and is just there to hold generated files.

Function CloudFormation

This is the Lambda Function part of the generated CloudFormation:

    "myHandler0D56A5FA": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
          },
          "S3Key": "5730888db11c312649e198855f33a572c8f61507cf6bfa67b9d1d98e01a83d4c.zip"
        },
...
        "Handler": "main",
        "MemorySize": 1024,
        "Runtime": "go1.x",
        "Timeout": 10
      },

If you have a simple configuration, you can be quite sure that the right CloudFormation is generated. But there are a few things that can go wrong:

  • Create wrong parameters if you dynamically generate them, e.g. from a file or in a loop
  • Does the program compiles, does it give an error?
  • Wrong usage of imports if the library definition changes
  • You know which CloudFormation should be generated, but you are unsure how the CDK syntax is
  • Breaking changes because CDK is very agile

Then a Unit test for the infrastructure is a good idea.

Let us assume we want to test the Function and there is only one function:

Test The Function

The test code is in the file dsl_test.go

Imports

  3 import (
  4   "testing"
  5   "github.com/aws/aws-sdk-go-v2/aws"
  6
  7   "github.com/aws/aws-cdk-go/awscdk/v2"
  8   assertions "github.com/aws/aws-cdk-go/awscdk/v2/assertions"
  9
 10   "dsl"
 11 )
Import  why
4 testing  testing basic
5 aws  just for pointer conversion / you could use jssi
7 cdk  to generate the stack
8 assertions helper for the assert checks itself
10 dsl  because the package is dsl_test we need to import dsl
 15 func TestInfraDslStack(t *testing.T) {
 16   // GIVEN
 17   app := awscdk.NewApp(nil)
 18
 19   // WHEN
 20   stack := dsl.NewDslStack(app, "MyStack", nil)
 21
 22   // THEN
 23   template := assertions.Template_FromStack(stack)
 24
 25   template.HasResourceProperties(aws.String("AWS::Lambda::Function"), map[string]interface{}{
 26     "Runtime": "go1.x",
 27   })
 28 }
Line  what
17 create the CDK APP
20 the creation of the stack, which is defined in package dsl. No aka “nil” options
23 generate the Template. This step is the slowest
25,26 Look into the function and test the property “runtime”

So this test checks for this line in the generated CloudFormation file:

"Runtime": "go1.x",

This seems a little basic. But you should have at least one test to check whether your CDK code has no errors and generates CloudFormation.

Run the test

export I_TEST=no
go test -v

Gives the output:

=== RUN   TestInfraDslStack
--- PASS: TestInfraDslStack (2.17s)
=== RUN   TestInfraLambdaExists
    integration_test.go:15: Skipping testing in non Integration environment
--- SKIP: TestInfraLambdaExists (0.00s)
PASS
ok  	dsl	2.706s

With the env variable “I_TEST” we control whether the integration test are run or not. The Integration tests have this code to control skippability:

if os.Getenv("I_TEST") != "yes" {
    t.Skip("Skipping testing in non Integration environment")
}

There are more helper functions in the assertion library - just experiment with different values to see the effects!

Now that we have a infrastructure Unit test, in the next chapter I create a Infrastructure Integration test.

See also

Source

See the full source on github.

Sources