Terraform Testing and Validation Techniques
Infrastructure code that is never tested accumulates hidden assumptions and silent failures. A misconfigured security group, a broken module output, or a wrong CIDR calculation might not reveal itself until it causes a production incident. Terraform provides several tools for catching problems early — from fast static checks to full end-to-end infrastructure tests. This topic covers each level of validation from quickest to most thorough.
The Testing Pyramid for Infrastructure
Diagram: Levels of Terraform Testing
/\
/ \
/ E2E \ ← terraform test (full apply in real cloud)
/--------\
/Integration\ ← module tests with real resources
/--------------\
/ Unit / Static \ ← fmt, validate, plan, checkov
/--------------------\
Faster, cheaper, safer at the bottom.
More realistic, more expensive at the top.
Start with the bottom layers — they catch the majority of errors in seconds. Add higher layers for critical modules and shared infrastructure code.
Level 1: terraform fmt
The fastest check. Terraform's formatter ensures your code follows the canonical style. Run it as a lint check in CI — fail the pipeline if the code is not properly formatted.
# Check format without modifying files (returns exit code 1 if unformatted) terraform fmt -check -recursive # Fix formatting automatically terraform fmt -recursive
Teams that run terraform fmt in CI never argue about indentation, alignment, or spacing in code reviews.
Level 2: terraform validate
Validates that your configuration is syntactically correct and internally consistent — no missing required arguments, no unknown attributes, no type mismatches. It does not contact any cloud provider.
terraform validate
Runs in seconds. Catches typos, wrong argument names, and type errors before any cloud API call is made.
Level 3: Variable Validation Blocks
Write validation rules directly in variable declarations to enforce business rules at plan time.
variable "environment" {
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Valid environments are: dev, staging, prod."
}
}
variable "instance_count" {
type = number
validation {
condition = var.instance_count >= 1 && var.instance_count <= 10
error_message = "Instance count must be between 1 and 10."
}
}
These run on every terraform plan. A wrong environment name or out-of-range count fails with your custom error message before any resource is evaluated.
Level 4: Static Analysis with Checkov
Checkov is an open-source static analysis tool that scans Terraform code for security and compliance misconfigurations. It checks hundreds of rules — S3 buckets without encryption, security groups open to the internet, missing access logs, and more.
# Install checkov pip install checkov # Scan current directory checkov -d . # Scan and output only failed checks checkov -d . --quiet
Example Checkov Output
Check: CKV_AWS_20: "Ensure the S3 bucket has access control list (ACL) applied" FAILED for resource: aws_s3_bucket.app_data File: /main.tf:15-25 Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled" FAILED for resource: aws_s3_bucket.app_data File: /main.tf:15-25
Add Checkov to CI pipelines as a gate — fail the PR if critical security checks do not pass.
Level 5: terraform test (Native Testing Framework)
Terraform 1.6 introduced a built-in testing framework. Write tests in .tftest.hcl files. Each test runs a real terraform apply in a temporary workspace, asserts conditions on the outputs or resource attributes, and then destroys everything.
Test File Structure
# tests/basic_vpc.tftest.hcl
run "creates_vpc_with_correct_cidr" {
command = apply
variables {
vpc_cidr = "10.0.0.0/16"
environment = "test"
}
assert {
condition = output.vpc_id != ""
error_message = "VPC ID should not be empty after apply"
}
assert {
condition = output.vpc_cidr == "10.0.0.0/16"
error_message = "VPC CIDR does not match the requested value"
}
}
Running Tests
terraform test
Terraform finds all .tftest.hcl files, runs each test case, reports passes and failures, and destroys all test resources. Tests that take too long or cost too much to run against real cloud can use command = plan instead of command = apply — running only to the plan stage without creating real infrastructure.
Level 6: Terratest (Go-based Integration Testing)
Terratest is an open-source Go library from Gruntwork for writing full integration tests for Terraform modules. Tests deploy real infrastructure, make HTTP requests, run SSH commands, and verify behaviour — then tear everything down.
// Example Terratest structure (Go)
func TestVpcModule(t *testing.T) {
opts := &terraform.Options{
TerraformDir: "../modules/networking",
Vars: map[string]interface{}{
"vpc_cidr": "10.0.0.0/16",
"environment": "test",
},
}
defer terraform.Destroy(t, opts)
terraform.InitAndApply(t, opts)
vpcID := terraform.Output(t, opts, "vpc_id")
assert.NotEmpty(t, vpcID)
}
Terratest is best suited for shared, widely-used module libraries where confidence in correctness justifies the extra complexity and cost of full cloud tests.
Key Points
- Use
terraform fmt -checkandterraform validateas fast first-pass checks in every CI pipeline. - Add variable validation blocks to enforce business rules at plan time with readable error messages.
- Run Checkov or a similar static analysis tool to catch security misconfigurations before deployment.
- Use the native
terraform testframework (Terraform 1.6+) for writing structured, repeatable module tests. - Apply testing effort proportionally — shared modules used across many teams deserve more thorough testing than single-use configurations.
