TL;DR
Learn how to test your infrastructure code using Terratest to ensure reliability and prevent issues
Infrastructure Testing with Terratest
Learn how to effectively test your infrastructure code using Terratest to ensure reliability and catch issues before they reach production.
$1
1. Catch configuration errors early
2. Validate infrastructure behavior
3. Ensure compliance and security
4. Test infrastructure changes safely
5. Automate validation processes
$1
$1
`` brew install go brew install terraform mkdir -p ~/go/src/terraform-aws-example
cd ~/go/src/terraform-aws-example
bash
`Install Go
Install Terraform
Set up Go workspace
$1
` terraform-aws-example/
├── main.tf
├── variables.tf
├── outputs.tf
├── test/
│ └── main_test.go
└── go.mod
`
$1
` // test/main_test.go
package test import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
) func TestTerraformBasicExample(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../",
Vars: map[string]interface{}{
"region": "us-west-2",
"instance_type": "t2.micro",
},
} defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
}
go
`
$1
$1
` func TestEC2Instance(t *testing.T) {
t.Parallel() terraformOptions := &terraform.Options{
TerraformDir: "../examples/ec2",
Vars: map[string]interface{}{
"instance_type": "t2.micro",
"ami_id": "ami-0c55b159cbfafe1f0",
},
} defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions) instanceID := terraform.Output(t, terraformOptions, "instance_id")
aws.WaitForInstanceRunning(t, instanceID, "us-west-2")
instanceType := aws.GetInstanceType(t, instanceID, "us-west-2")
assert.Equal(t, "t2.micro", instanceType)
}
go
`
$1
` func TestVPCConfiguration(t *testing.T) {
t.Parallel() terraformOptions := &terraform.Options{
TerraformDir: "../examples/vpc",
Vars: map[string]interface{}{
"vpc_cidr": "10.0.0.0/16",
"environment": "test",
},
} defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions) vpcID := terraform.Output(t, terraformOptions, "vpc_id")
cidrBlock := aws.GetVpcCidrBlock(t, vpcID, "us-west-2")
assert.Equal(t, "10.0.0.0/16", cidrBlock)
}
go
`
$1
` func TestS3Bucket(t *testing.T) {
t.Parallel() terraformOptions := &terraform.Options{
TerraformDir: "../examples/s3",
Vars: map[string]interface{}{
"bucket_name": "test-bucket",
"versioning_enabled": true,
},
} defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions) bucketName := terraform.Output(t, terraformOptions, "bucket_name")
versioning := aws.GetS3BucketVersioning(t, "us-west-2", bucketName)
assert.Equal(t, "Enabled", versioning)
}
go
`
$1
$1
` func TestWebServer(t *testing.T) {
t.Parallel() terraformOptions := &terraform.Options{
TerraformDir: "../examples/web-server",
} defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions) url := terraform.Output(t, terraformOptions, "url")
http_helper.HttpGetWithRetry(t, url, nil, 200, "Hello, World!", 30, 5*time.Second)
}
go
`
$1
` func TestRDSInstance(t *testing.T) {
t.Parallel() terraformOptions := &terraform.Options{
TerraformDir: "../examples/rds",
Vars: map[string]interface{}{
"db_name": "testdb",
"db_user": "admin",
"db_password": "password123",
},
} defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions) dbEndpoint := terraform.Output(t, terraformOptions, "db_endpoint")
dbPort := terraform.Output(t, terraformOptions, "db_port")
// Test database connection
connectionString := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s",
dbEndpoint, dbPort, "admin", "password123", "testdb",
)
db, err := sql.Open("postgres", connectionString)
assert.NoError(t, err)
defer db.Close()
err = db.Ping()
assert.NoError(t, err)
}
go
`
$1
` func TestLoadBalancer(t *testing.T) {
t.Parallel() terraformOptions := &terraform.Options{
TerraformDir: "../examples/alb",
} defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions) albDNS := terraform.Output(t, terraformOptions, "alb_dns_name")
// Test load balancer health
http_helper.HttpGetWithRetryWithCustomValidation(
t,
fmt.Sprintf("http://%s", albDNS),
nil,
30,
5*time.Second,
func(status int, body string) bool {
return status == 200
},
)
}
go
`
$1
$1
` func TestInParallel(t *testing.T) {
t.Parallel() testCases := []struct {
name string
region string
environment string
}{
{"DevEnvironment", "us-west-2", "dev"},
{"StagingEnvironment", "us-east-1", "staging"},
{"ProdEnvironment", "eu-west-1", "prod"},
} for _, tc := range testCases {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
terraformOptions := &terraform.Options{
TerraformDir: "../",
Vars: map[string]interface{}{
"region": tc.region,
"environment": tc.environment,
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
})
}
}
go
`
$1
` func TestWithRetry(t *testing.T) {
t.Parallel() terraformOptions := &terraform.Options{
TerraformDir: "../examples/ec2",
} defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions) instanceID := terraform.Output(t, terraformOptions, "instance_id")
retry.DoWithRetry(t, "Check instance tags", 30, 5*time.Second, func() (string, error) {
tags := aws.GetTagsForEc2Instance(t, instanceID, "us-west-2")
if val, ok := tags["Environment"]; ok {
return val, nil
}
return "", fmt.Errorf("Environment tag not found")
})
}
go
`
$1
` func TestWithCleanup(t *testing.T) {
t.Parallel() tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, "../", "examples/cleanup-test")
defer test_structure.CleanupTestDataFolder(t, tempTestFolder)
terraformOptions := &terraform.Options{
TerraformDir: tempTestFolder,
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
}
go
`
$1
$1
` name: Terratest on: [push, pull_request] jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
- name: Install Terraform
uses: hashicorp/setup-terraform@v2
- name: Run Terratest
run: |
cd test
go test -v ./...
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
yaml
`
$1
$1
` go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
bash
`Generate test coverage report
$1
` func TestWithLogs(t *testing.T) {
logger.Log(t, "Starting test...")
terraformOptions := &terraform.Options{
TerraformDir: "../",
Logger: logger.TestingT,
}
logger.Log(t, "Applying Terraform configuration...")
terraform.InitAndApply(t, terraformOptions)
logger.Log(t, "Test completed successfully")
}
go
``
$1
Effective infrastructure testing:
Remember to:
1. Write comprehensive tests
2. Use proper cleanup
3. Implement retry logic
4. Test in parallel when possible
5. Integrate with CI/CD pipelines
Why This Matters
Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.
Trade-offs to Consider
Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.
When NOT to Use This
Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.
Decision Framework
Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.