Testing Terraform Configurations (tftest and Terratest)
As infrastructure scale grows, manual verification of cloud resources becomes impossible. Deploying untested Infrastructure as Code (IaC) to production is just as risky as deploying untested application code. To mitigate this risk, modern DevOps practices require automated testing pipelines for Terraform configurations.
In this guide, you will learn how to transition from manual "plan-and-pray" deployments to robust, automated testing strategies using Terraform's native test framework (introduced in Terraform 1.6) and Terratest, the industry-standard Go-based testing library.
The Infrastructure Testing Pyramid
Just like software engineering, infrastructure testing follows a pyramid structure. Testing is divided into three main layers: static analysis, unit/contract testing, and integration testing.
+--------------------------------------------------+
| Integration Testing |
| (Terratest / Go) |
| - Deploys real resources to live cloud sandbox |
| - Validates actual API behavior and endpoints |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| Unit & Contract Testing |
| (Native terraform test) |
| - Validates inputs, outputs, and plan behavior |
| - Uses mock providers or dry-run configurations |
+--------------------------------------------------+
|
v
+--------------------------------------------------+
| Static Analysis |
| (terraform validate, tflint) |
| - Checks syntax, style, and security policies |
+--------------------------------------------------+
By implementing these layers, you catch syntax errors instantly, validate logical assertions during development, and ensure that live cloud APIs behave exactly as expected before merging code to your main branch.
Native Terraform Testing with terraform test
Starting with Terraform 1.6, HashiCorp introduced a native test framework. This framework allows you to write tests using HCL (HashiCorp Configuration Language) without needing to learn external programming languages like Go or Python. These test files use the .tftest.hcl extension.
How Native Testing Works
The native testing framework operates by executing temporary runs of your configurations. It applies your code to a test environment, runs assertions against the resulting state or plan, and then automatically destroys the resources it created.
Code Example: Writing a Native Terraform Test
Consider a simple Terraform module that creates an AWS S3 bucket with encryption enabled. Here is the main configuration file:
# main.tf
resource "aws_s3_bucket" "secure_bucket" {
bucket = var.bucket_name
}
resource "aws_s3_bucket_server_side_encryption_configuration" "encryption" {
bucket = aws_s3_bucket.secure_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
output "bucket_name" {
value = aws_s3_bucket.secure_bucket.id
}
To verify that this bucket is always created with the correct encryption algorithm, you can write a test file inside a directory named tests/.
# tests/s3_encryption.tftest.hcl
variables {
bucket_name = "my-automated-test-bucket-xyz123"
}
run "validate_encryption_on_apply" {
command = apply
assert {
condition = aws_s3_bucket_server_side_encryption_configuration.encryption.rule[0].apply_server_side_encryption_by_default[0].sse_algorithm == "AES256"
error_message = "S3 bucket encryption algorithm must be set to AES256."
}
assert {
condition = can(regex("^my-automated-test-bucket", output.bucket_name))
error_message = "The bucket name output does not match the expected prefix."
}
}
To run this test suite, execute the following command in your terminal:
terraform test
Terraform will initialize the providers, apply the configuration, evaluate the assertions, and clean up the resources automatically, returning a detailed test report.
Advanced Testing with Terratest
While native HCL testing is excellent for unit tests and simple validations, complex scenarios require real-world integration testing. Terratest is an open-source Go library developed by Gruntwork that allows you to write automated tests for your infrastructure using real programming logic.
With Terratest, you can deploy real infrastructure, make HTTP requests to the deployed endpoints, ssh into virtual machines to run commands, and verify database connections.
Code Example: Writing a Terratest in Go
To use Terratest, you must have Go installed on your machine. Create a test file named s3_bucket_test.go inside a test/ directory.
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestS3BucketEncryption(t *testing.T) {
t.Parallel()
// Configure the options for Terraform
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": "terratest-validation-bucket-998877",
},
})
// At the end of the test, run 'terraform destroy' to clean up resources
defer terraform.Destroy(t, terraformOptions)
// Run 'terraform init' and 'terraform apply'. Fail the test if there are any errors.
terraform.InitAndApply(t, terraformOptions)
// Run 'terraform output' to get the value of an output variable
bucketName := terraform.Output(t, terraformOptions, "bucket_name")
// Verify that the bucket name matches our expectation
assert.Equal(t, "terratest-validation-bucket-998877", bucketName)
}
To run this test, initialize your Go modules and execute the test command:
go mod init test
go mod tidy
go test -v -timeout 30m
Terratest will invoke the Terraform CLI behind the scenes, provision the S3 bucket in your active AWS account, perform the assertions, and guarantee the destruction of the resources via Go's defer mechanism.
Real-World Use Cases
- Multi-Region Module Validation: Ensure that your VPC and networking modules can be deployed across different cloud regions (e.g., us-east-1 and eu-west-1) without CIDR block overlapping or naming collisions.
- Security Group Compliance: Write assertions that fail the test suite if any security group rule opens port 22 (SSH) or port 3389 (RDP) to the public internet (0.0.0.0/0).
- Application End-to-End Verification: Use Terratest to deploy an Auto Scaling Group behind an Application Load Balancer, wait for the instances to become healthy, make an HTTP request to the load balancer's DNS name, and assert that the response status code is 200 OK.
Common Mistakes and How to Avoid Them
- Orphaned Resources (Stray Infrastructure): If an integration test crashes, fails midway, or is manually interrupted, it may leave active resources running in your cloud provider. Solution: Implement automated cleanup scripts or use tools like
aws-nukein your sandbox accounts to wipe out daily leftover resources. - Testing in Production Environments: Running tests in production or staging environments can cause downtime or resource conflicts. Solution: Always run automated tests in isolated, dedicated sandboxed cloud accounts or subscription spaces.
- Slow Feedback Loops: Running end-to-end integration tests on every single commit can slow down development teams. Solution: Run lightweight native
terraform testruns on pull requests, and reserve heavy Terratest suites for nightly builds or release candidate branches. - Hardcoded Credentials: Hardcoding API keys or AWS credentials inside test configurations. Solution: Use environment variables and temporary IAM roles assumed via OIDC within your CI/CD pipelines.
Interview Notes and Questions
- What is the difference between terraform plan and terraform test?
terraform planshows what changes will be made to your infrastructure based on state differences, but it does not validate the functional behavior of those resources.terraform testactually provisions resources (or mocks them) and runs logical assertions against outputs and attributes to verify behavior. - How do you handle state files during automated testing? Automated tests should generate unique, randomized state files for each run (often handled automatically by the test frameworks creating temporary directories) to prevent state locking or state corruption of your actual environments.
- When should you choose Terratest over native HCL testing? Choose native HCL testing for fast, declarative assertions on modules, input validation, and structural checks. Choose Terratest when you need to write complex programmatic logic, validate actual OS-level configurations (like SSHing into an EC2 instance), or test API responses from external systems.
Summary
Automated testing is the cornerstone of reliable Infrastructure as Code. By leveraging native terraform test configurations, you can easily validate structural and logical constraints using HCL. For advanced, end-to-end validation that interacts with real-world endpoints, Terratest provides a powerful, Go-driven framework to ensure your infrastructure behaves exactly as intended before it ever reaches production.