terraform plan only checks syntax; it does not guarantee that the deployed Load Balancer actually routes traffic correctly. It is time to implement actual testing using Terratest.1. The Need for Infrastructure Testing#
Consider the VPC Module we built back in Episode 9. Suppose a junior engineer modifies the module to add an extra subnet. They run terraform plan, and it succeeds. They merge the PR.
However, they accidentally misconfigured the Route Table, meaning the subnet has no internet access. terraform plan cannot catch this error because the AWS API accepts the configuration as valid. You only discover the outage when Production crashes.
Terratest, an open-source Go library developed by Gruntwork, solves this. It allows you to write Go code that:
- Programmatically runs
terraform initandterraform applyin a temporary test environment. - Uses the AWS SDK (via Go) to reach out to the physical cloud and verify if the resource behaves exactly as expected (e.g., actually making an HTTP GET request to the Load Balancer to ensure it returns a
200 OK). - Always runs
terraform destroyat the end, regardless of whether the test passed or failed.
2. Setting Up the Go Environment#
Because Terratest is a Go library, you must have Golang installed on your machine.
Step 2.1: Install Golang#
Ensure Go is installed. If you are on Ubuntu:
sudo snap install go --classic
go version
# Expected: go version go1.21.0 linux/amd64Step 2.2: The Directory Structure#
Tests in Terraform modules are traditionally placed in a dedicated test/ folder adjacent to your .tf files.
terraform-testing/
├── main.tf
├── outputs.tf
├── variables.tf
└── test/
├── go.mod
├── go.sum
└── web_server_test.go # <--- The Terratest Code3. Practice: Writing the Terraform Module#
Let’s write a simple Terraform module that creates an AWS S3 bucket. We will intentionally make it simple so we can focus on the testing mechanics.
Create a new directory and files:
mkdir -p terraform-testing/test
cd terraform-testingmain.tf
variable "bucket_name" {
type = string
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "test_bucket" {
bucket = var.bucket_name
}outputs.tf
output "bucket_arn" {
value = aws_s3_bucket.test_bucket.arn
}4. Practice: Writing the Terratest (Golang) Code#
Now, navigate into the test/ directory to write our Go test.
cd testInitialize the Go module and download the Terratest dependencies:
go mod init my-terraform-tests
go get github.com/gruntwork-io/terratest/modules/terraform
go get github.com/gruntwork-io/terratest/modules/aws
go get github.com/stretchr/testify/assertCreate a file named web_server_test.go and paste the following Go code:
package test
import (
"fmt"
"strings"
"testing"
"time"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestTerraformS3Bucket(t *testing.T) {
t.Parallel()
// 1. Generate a random, unique name for the bucket to prevent test collisions
expectedBucketName := fmt.Sprintf("terratest-demo-bucket-%d", time.Now().Unix())
// 2. Define the Terraform Options
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
// The path to where our Terraform code is located
TerraformDir: "../",
// Variables to pass to our Terraform code using -var options
Vars: map[string]interface{}{
"bucket_name": expectedBucketName,
},
})
// 3. Clean up the infrastructure at the end of the test.
// defer ensures this runs even if the assertions below fail!
defer terraform.Destroy(t, terraformOptions)
// 4. Run `terraform init` and `terraform apply`
terraform.InitAndApply(t, terraformOptions)
// 5. Run `terraform output` to get the value of an output variable
bucketARN := terraform.Output(t, terraformOptions, "bucket_arn")
// 6. Assertions!
// We use the AWS SDK (via Terratest) to actually look inside the AWS account
// and verify the bucket exists and matches our expectations.
aws.AssertS3BucketExists(t, "us-east-1", expectedBucketName)
// We verify the output ARN matches standard AWS format
assert.True(t, strings.HasPrefix(bucketARN, "arn:aws:s3:::"))
assert.True(t, strings.HasSuffix(bucketARN, expectedBucketName))
}5. Execution and Observation#
To run the test, ensure your AWS CLI credentials are fully configured (as learned in Episode 2), and execute the standard Go test command from inside the test/ directory.
go test -v -timeout 30mExpected Terminal Output:
=== RUN TestTerraformS3Bucket
=== PAUSE TestTerraformS3Bucket
=== CONT TestTerraformS3Bucket
TestTerraformS3Bucket 2023-10-24T10:00:00Z retry.go:91: terraform [init -upgrade=false]
TestTerraformS3Bucket 2023-10-24T10:00:00Z logger.go:66: Running command terraform with args [init -upgrade=false]
...
TestTerraformS3Bucket 2023-10-24T10:00:03Z retry.go:91: terraform [apply -auto-approve -var bucket_name=terratest-demo-bucket-1698141600]
...
TestTerraformS3Bucket 2023-10-24T10:00:05Z logger.go:66: aws_s3_bucket.test_bucket: Creation complete after 1s [id=terratest-demo-bucket-1698141600]
TestTerraformS3Bucket 2023-10-24T10:00:05Z logger.go:66: Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
...
TestTerraformS3Bucket 2023-10-24T10:00:05Z retry.go:91: terraform [output -no-color -json bucket_arn]
TestTerraformS3Bucket 2023-10-24T10:00:06Z logger.go:66: Asserting S3 bucket terratest-demo-bucket-1698141600 exists in us-east-1
...
TestTerraformS3Bucket 2023-10-24T10:00:07Z retry.go:91: terraform [destroy -auto-approve -var bucket_name=terratest-demo-bucket-1698141600]
...
TestTerraformS3Bucket 2023-10-24T10:00:10Z logger.go:66: Destroy complete! Resources: 1 destroyed.
--- PASS: TestTerraformS3Bucket (10.45s)
PASS
ok my-terraform-tests 10.460sLook at that beautiful timeline! The Go code initialized Terraform, created the bucket, asserted its existence in the AWS API, and successfully destroyed it all in exactly 10.46 seconds.
If you integrate this go test command into the GitHub Actions pipeline we built in Episode 12, your Terraform Modules will never break Production again.
Troubleshooting & Common Errors#
go: go.mod file not found in current directory or any parent directory- Root Cause: You forgot to initialize the Go module.
- Solution: Run
go mod init <module-name>inside yourtest/directory before runninggo getorgo test.
Test timed outorOrphaned Resources- Root Cause: If your Go test crashes (e.g., due to a syntax error or forced termination
Ctrl+C) before thedefer terraform.Destroy()command executes, the infrastructure will remain running in AWS forever, costing you money. - Solution: Gruntwork provides an advanced tool called
cloud-nukespecifically designed to sweep your AWS accounts and destroy any orphaned resources left behind by failed Terratests.
- Root Cause: If your Go test crashes (e.g., due to a syntax error or forced termination
Curriculum Conclusion 🎓#
You have reached the absolute pinnacle of this curriculum.
Over the past 15 episodes, you have evolved from manually clicking through the AWS Console, to declaring basic resources, building dynamic modules and loops, scaling via terragrunt, and finally reaching the holy grail of Platform Engineering: Automated Infrastructure Unit Testing.
You are no longer an operator; you are a Senior Platform Engineer.
Thank you for embarking on this incredible Zero to Hero journey. Your cloud infrastructure will never be the same again.

