Infrastructure integration testing is becoming a standard practice: Gruntwork reports that Terratest has over 15,000 GitHub stars and is used by teams at Amazon, Google, and Lyft to validate their infrastructure modules before production deployment. The key insight behind Terratest is that Terraform’s native testing validates state — but state and reality can diverge when provider bugs, cloud API delays, or race conditions cause resources to be created incorrectly while Terraform reports success. Terratest queries actual cloud APIs to verify that your S3 bucket not only “exists” in state but is actually accessible, encrypted, and configured correctly. This Go-based approach requires learning Go but provides the strongest possible guarantee: your infrastructure works in the real cloud, with real API responses, under real conditions. This guide covers the complete Terratest workflow from setup to parallel execution patterns.
TL;DR
- Terratest tests actual infrastructure via cloud APIs—not Terraform state—catching bugs that native testing misses
- The
defer terraform.Destroypattern guarantees cleanup even when tests fail, preventing orphaned resources- Test stages let you skip slow steps during local development (skip AMI build, reuse deployed infra)
Best for: Teams needing to validate real cloud behavior, not just configuration correctness
Skip if: You only need to validate Terraform syntax/logic (use native
terraform testinstead)Read time: 12 minutes
Here’s a hard truth about infrastructure testing: Terraform’s native test framework validates state, not reality. If a provider bug creates a misconfigured resource but reports success, your tests pass while your infrastructure fails.
Terratest solves this by querying actual cloud resources through their native APIs. Your S3 bucket isn’t just “created” in Terraform state—Terratest verifies it actually exists, has the right encryption, and serves content correctly.
Prerequisites
Before starting, ensure you have:
- Go 1.21+ installed (
go version) - Terraform 1.0+ installed (
terraform version) - AWS CLI configured with credentials (
aws sts get-caller-identity) - Basic Go knowledge (functions, structs, error handling)
- A dedicated AWS account or sandbox for testing
Step 1: Project Setup
Create a new directory structure for your Terraform module and tests:
mkdir -p terraform-s3-module/test
cd terraform-s3-module
Initialize the Go module:
cd test
go mod init github.com/yourorg/terraform-s3-module/test
go get github.com/gruntwork-io/terratest/modules/terraform
go get github.com/gruntwork-io/terratest/modules/aws
go get github.com/stretchr/testify/assert
Your go.mod should now reference Terratest v0.53.0 or later.
Step 2: Create a Terraform Module to Test
Create main.tf in the root directory:
variable "bucket_name" {
description = "Name of the S3 bucket"
type = string
}
variable "environment" {
description = "Environment tag"
type = string
default = "test"
}
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = aws_s3_bucket.this.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
output "bucket_id" {
value = aws_s3_bucket.this.id
}
output "bucket_arn" {
value = aws_s3_bucket.this.arn
}
Step 3: Write Your First Terratest
Create test/s3_bucket_test.go:
package test
import (
"fmt"
"testing"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestS3BucketCreation(t *testing.T) {
t.Parallel()
// Generate unique bucket name to avoid conflicts
uniqueID := random.UniqueId()
bucketName := fmt.Sprintf("terratest-example-%s", uniqueID)
awsRegion := "us-west-2"
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../",
Vars: map[string]interface{}{
"bucket_name": bucketName,
"environment": "test",
},
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": awsRegion,
},
})
// CRITICAL: Always clean up resources
defer terraform.Destroy(t, terraformOptions)
// Deploy infrastructure
terraform.InitAndApply(t, terraformOptions)
// Get outputs
bucketID := terraform.Output(t, terraformOptions, "bucket_id")
// Validate ACTUAL infrastructure via AWS API
aws.AssertS3BucketExists(t, awsRegion, bucketID)
// Verify versioning is actually enabled (not just in state)
versioning := aws.GetS3BucketVersioning(t, awsRegion, bucketID)
assert.Equal(t, "Enabled", versioning)
// Verify encryption configuration
encryption := aws.GetS3BucketEncryption(t, awsRegion, bucketID)
assert.Equal(t, "AES256", encryption)
}
Key patterns in this test:
t.Parallel()— Runs tests concurrently for faster executionrandom.UniqueId()— Prevents resource name collisionsdefer terraform.Destroy()— Guarantees cleanup even on failureaws.AssertS3BucketExists()— Validates real AWS resources, not state
Step 4: Run the Test
Execute from the test directory:
go test -v -timeout 30m
Expected output:
=== RUN TestS3BucketCreation
=== PAUSE TestS3BucketCreation
=== CONT TestS3BucketCreation
TestS3BucketCreation 2026-01-11T10:15:30Z command.go:158: Running command terraform with args [init]
TestS3BucketCreation 2026-01-11T10:15:35Z command.go:158: Running command terraform with args [apply -auto-approve]
...
TestS3BucketCreation 2026-01-11T10:16:45Z command.go:158: Running command terraform with args [destroy -auto-approve]
--- PASS: TestS3BucketCreation (95.23s)
PASS
Step 5: Test Stages for Faster Iteration
Real-world tests are slow. Building AMIs, deploying clusters, and running validations can take 30+ minutes. Test stages let you skip completed stages during development:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
)
func TestWithStages(t *testing.T) {
t.Parallel()
workingDir := test_structure.CopyTerraformFolderToTemp(t, "../", ".")
// Stage: Deploy
defer test_structure.RunTestStage(t, "teardown", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, workingDir)
terraform.Destroy(t, terraformOptions)
})
test_structure.RunTestStage(t, "deploy", func() {
uniqueID := random.UniqueId()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: workingDir,
Vars: map[string]interface{}{
"bucket_name": fmt.Sprintf("terratest-%s", uniqueID),
},
})
test_structure.SaveTerraformOptions(t, workingDir, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
})
// Stage: Validate
test_structure.RunTestStage(t, "validate", func() {
terraformOptions := test_structure.LoadTerraformOptions(t, workingDir)
bucketID := terraform.Output(t, terraformOptions, "bucket_id")
aws.AssertS3BucketExists(t, "us-west-2", bucketID)
})
}
Skip stages during development:
# Skip deploy, only run validation (assumes infra exists)
SKIP_deploy=true go test -v -run TestWithStages
# Skip teardown to keep resources for debugging
SKIP_teardown=true go test -v -run TestWithStages
AI-Assisted Approaches
In 2026, AI accelerates Terratest development significantly.
What AI does well:
- Generating boilerplate test structure from Terraform modules
- Writing assertions based on resource configurations
- Suggesting edge cases (what if bucket name has special characters?)
- Converting HCL outputs to Go struct mappings
What still needs humans:
- Deciding what behaviors actually matter to test
- Understanding blast radius if tests fail in production accounts
- Architecting test isolation strategies
- Reviewing AI-generated assertions for false positives
Useful prompt:
Given this Terraform module that creates an RDS instance:
resource "aws_db_instance" "main" {
identifier = var.db_identifier
engine = "postgres"
engine_version = "15.4"
instance_class = var.instance_class
allocated_storage = 20
storage_encrypted = true
deletion_protection = var.enable_deletion_protection
}
Generate a Terratest that:
1. Deploys the RDS instance with test variables
2. Validates encryption is actually enabled via AWS API
3. Verifies the correct Postgres version is running
4. Tests that deletion protection is set correctly
5. Uses proper cleanup with defer
Include error handling and meaningful assertion messages.
Decision Framework
When to Use Terratest
This approach works best when:
- You need to validate actual cloud resource behavior
- Testing interactions between multiple services (VPC + EC2 + RDS)
- Your team has Go experience or willingness to learn
- Integration tests are critical to your deployment pipeline
- You’re testing Kubernetes, Docker, or Packer alongside Terraform
Consider alternatives when:
- You only need to validate Terraform logic (use
terraform test) - Fast feedback is critical and Go learning curve is too steep
- Your infrastructure is simple (<5 resources)
- Cost constraints limit ephemeral resource deployment
Terratest vs Terraform Native Test
| Aspect | Terratest | Terraform Test |
|---|---|---|
| What it tests | Actual infrastructure via APIs | Terraform state |
| Language | Go | HCL |
| Learning curve | Higher | Lower |
| Catches provider bugs | Yes | No |
| Multi-tool support | Terraform, K8s, Docker, Packer | Terraform/OpenTofu only |
| Speed | Slower (real deploys) | Faster (plan-based) |
My recommendation: Use both. Terraform native tests for fast unit testing of module logic. Terratest for integration tests that validate real infrastructure before production deployments.
Measuring Success
| Metric | Baseline | Target | How to Track |
|---|---|---|---|
| Test coverage | 0% | 80%+ of critical modules | Count tested vs total modules |
| Test runtime | N/A | < 30 min for full suite | CI pipeline metrics |
| Orphaned resources | Unknown | 0 | AWS Cost Explorer tags |
| Flaky test rate | High | < 5% | CI failure analysis |
| Mean time to detect issues | Days | Hours | Incident tracking |
Warning signs:
- Tests pass but production deployments fail — tests aren’t comprehensive enough
- Orphaned resources accumulating — cleanup not working properly
- Tests take > 1 hour — need to parallelize or use test stages
- High flakiness — missing retry logic or timing issues
Common Pitfalls
1. Missing defer Destroy
// BAD: If Apply fails, resources are orphaned
terraform.InitAndApply(t, options)
terraform.Destroy(t, options) // Never reached if Apply fails
// GOOD: Cleanup runs regardless of test outcome
defer terraform.Destroy(t, options)
terraform.InitAndApply(t, options)
2. Resource Name Collisions
// BAD: Will conflict with other tests or runs
bucketName := "my-test-bucket"
// GOOD: Unique per test run
uniqueID := random.UniqueId()
bucketName := fmt.Sprintf("test-%s", uniqueID)
3. Hardcoded Regions
// BAD: Fails if default region differs
aws.AssertS3BucketExists(t, "us-east-1", bucketID)
// GOOD: Consistent with Terraform deployment
awsRegion := terraform.Output(t, options, "region")
aws.AssertS3BucketExists(t, awsRegion, bucketID)
4. Insufficient Timeouts
# BAD: Default 10m timeout kills long-running tests
go test -v
# GOOD: Allow time for real infrastructure
go test -v -timeout 30m
“Terraform state says your infrastructure exists. Terratest proves it works. That difference matters when the alternative is discovering a misconfigured VPC in a production incident at 2 AM.” — Yuri Kan, Senior QA Lead
What’s Next
Start with one critical module—your VPC, primary database, or main compute cluster. Write a single test that validates it actually works, not just deploys. Run it in CI before every merge.
Once comfortable, expand to test interactions: can your application actually connect to that database? Does the load balancer route traffic correctly?
The goal isn’t 100% coverage—it’s confidence that your infrastructure works in reality, not just in Terraform’s view of reality.
Related articles:
- Pulumi Testing Best Practices
- Terraform Testing and Validation Strategies
- Test Automation Pyramid Strategy
- CloudFormation Template Testing
External resources:
FAQ
What is Terratest and how does it work?
Terratest is a Go library from Gruntwork that enables integration testing for infrastructure code. It deploys real infrastructure via Terraform, queries actual cloud resources through their APIs, runs assertions, and destroys everything with defer terraform.Destroy. Unlike Terraform’s native test framework, Terratest validates actual cloud behavior, not just state. Gruntwork reports 15,000+ GitHub stars and adoption by teams at Amazon, Google, and Lyft.
How much does running Terratest cost in AWS?
Most Terratest test suites for a single module run 5-15 minutes and cost $0.10-$2.00 per run depending on resources. Use test stages to skip re-deployment during local development, run tests in parallel to reduce total time, and use lower-cost instance types for testing. Dedicated test AWS accounts with cost alerts prevent surprises.
Can Terratest be used with Terraform Cloud or other IaC tools?
Yes. Terratest supports Terraform, Terragrunt, Packer, Docker, Kubernetes, and other tools. It includes helpers for AWS, GCP, Azure, and Kubernetes API verification. The core pattern (deploy, verify, destroy) works regardless of the IaC tool.
How do you prevent orphaned resources from Terratest runs?
Use defer terraform.Destroy(t, terraformOptions) immediately after configuring options — Go’s defer guarantees execution even on test failure. Run Terratest in dedicated test accounts with automated cleanup scripts (cloud-nuke from Gruntwork) as a safety net for tests that panic unexpectedly.
Official Resources
- Terratest Documentation — official Gruntwork documentation
- Terraform Documentation — Terraform reference
- Terratest GitHub — source code with examples
- Terratest Go Package — API reference
See Also
- Infrastructure as Code Testing: Validation Strategies for Terraform and Ansible - IaC testing: Terraform validation, Terratest, kitchen-terraform,…
- Cost Estimation Testing for Infrastructure as Code: Complete Guide - Master cost estimation testing for IaC with Infracost, terraform…
- Cloud Resource Tagging Validation: Automated Compliance Testing - Learn how to validate cloud resource tags across AWS, Azure, and…
- Terraform Testing and Validation Strategies: Complete DevOps Guide - Master Terraform testing with comprehensive validation strategies,…
