The integ-tests-alpha module is designed to simplify the process of writing integration tests for AWS CDK applications, particularly for testing the deployment and behavior of your CloudFormation stacks in a real AWS environment. It abstracts away much of the boilerplate code typically required for this, allowing you to focus on the test logic itself.
Let’s see integ-tests-alpha in action. Imagine you have a CDK stack that deploys an S3 bucket. You want to write an integration test to ensure the bucket is created and that you can write a file to it.
Here’s a simplified example of how such a test might look using integ-tests-alpha:
from aws_cdk import Stack
from constructs import Construct
from integ_tests_alpha import IntegTest, ExpectedOutput, FileAssetDistribution, StackAssertions
class MyBucketStack(Stack):
def __init__(self, scope: Construct, construct_id: str):
super().__init__(scope, construct_id)
from aws_cdk import aws_s3 as s3
bucket = s3.Bucket(self, "MyTestBucket")
# Expose bucket name as an output for the integration test
ExpectedOutput(self, "BucketNameOutput",
value=bucket.bucket_name,
export_name="MyTestBucketName")
class TestMyBucketStack(IntegTest):
def __init__(self, scope: Construct, construct_id: str):
super().__init__(scope, construct_id)
# Deploy the stack to be tested
my_stack = MyBucketStack(self, "MyBucketStack")
# Assertions for the deployed stack
StackAssertions(self, "MyBucketStackAssertions",
stack=my_stack,
test_cases=[
{
"name": "bucket-created",
"assertion": lambda stack: ExpectedOutput.assert_has_output(
stack, "BucketNameOutput"
)
}
])
# Example of interacting with the deployed resource (writing to the bucket)
# This assumes you have the AWS SDK (boto3) installed and configured.
self.add_test_step("write-to-bucket",
commands=[
# Get the bucket name from the CloudFormation output
"BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name ${AWS_STACK_NAME} --query \"Stacks[0].Outputs[?OutputKey=='BucketNameOutput'].OutputValue\" --output text)",
# Write a file to the bucket
"echo 'hello integration test' > test.txt",
"aws s3 cp test.txt s3://${BUCKET_NAME}/test.txt",
# Verify the file exists
"aws s3 ls s3://${BUCKET_NAME}/test.txt"
],
# Use a specific Docker image with necessary tools
# In a real scenario, you might need to build a custom image
# or use an existing one that has aws cli, jq, etc.
docker_image=FileAssetDistribution.from_asset("test/integ/my-test-image")
)
# To run this test:
# 1. Ensure you have a Dockerfile in 'test/integ/my-test-image' that builds
# an image with AWS CLI and other necessary tools.
# 2. Run:
# cdk deploy --app "python app.py" # Deploy your app
# cdk integ-test --app "python app.py" --profile <your-aws-profile>
The core problem integ-tests-alpha solves is bridging the gap between your high-level CDK constructs and the actual AWS resources they provision. Writing integration tests for cloud infrastructure is inherently complex: you need to deploy resources, interact with them in a stateful way, and then clean them up. integ-tests-alpha provides a structured way to manage this lifecycle. It leverages CloudFormation under the hood to deploy your stacks and then provides a framework to execute arbitrary commands (often within a Docker container) against those deployed resources.
Internally, when you run cdk integ-test, the integ-tests-alpha module does several things. First, it synthesizes your CDK app into CloudFormation templates. Then, it deploys these templates to your AWS account using CloudFormation, creating a dedicated stack for your integration tests. This test stack often contains your application stack(s) as nested stacks. Crucially, it also sets up any necessary IAM roles or permissions for your tests to interact with the deployed resources. After deployment, it provisions a Docker container (or uses an existing one if specified) where your test commands are executed. These commands can interact with AWS services using the AWS CLI, SDKs, or any other tools you package into the container. Finally, upon test completion (or failure), it attempts to tear down the deployed resources.
The primary lever you control is the add_test_step method. Each add_test_step defines a sequence of commands to be executed within the test container. You can chain multiple steps, and the output of one step can often be used as input for the next, typically by capturing AWS CLI output into shell variables. You also define your test stacks and assertions using StackAssertions, which allow you to check for the existence of outputs, resources, or even specific resource properties after deployment. The docker_image parameter in add_test_step is critical for ensuring your test environment has the necessary tools.
When you define ExpectedOutput in your application stack, you’re essentially creating a contract between your application and your integration tests. These outputs are then accessible via the AWS CLI within your test steps, allowing you to dynamically retrieve information about the deployed resources, such as bucket names, ARNs, or endpoint URLs. This makes your tests robust, as they don’t rely on hardcoded values.
The most surprising aspect of integ-tests-alpha is how it handles resource cleanup. By default, it ties the lifecycle of your integration test resources to the integration test stack itself. When the integration test stack is deleted (usually after the test run or via cdk destroy), CloudFormation attempts to delete all resources defined within it. However, this can sometimes lead to issues if resources are not properly configured for deletion or if permissions are insufficient. For instance, an S3 bucket with versioning enabled and objects remaining will not be deleted by CloudFormation until those objects are removed. This is why in complex scenarios, you might need to explicitly add cleanup steps within your add_test_step commands or manage resource deletion outside of the standard CloudFormation flow.
The next concept you’ll likely encounter is managing sensitive information and credentials within your integration test environment, especially when dealing with private resources or external dependencies.