Describe Terraform's 'terraform test' framework. How does it differ from Terratest, and how would you implement a comprehensive testing strategy for a Terraform module that provisions a VPC with public/private subnets?
Quick Answer
Terraform's native test framework (since v1.6) uses .tftest.hcl files with run blocks that execute real plan/apply cycles with assertions on outputs and resource attributes. Unlike Terratest (Go-based), it doesn't require programming knowledge. A comprehensive strategy combines terraform validate, terraform test, and integration tests.
Detailed Answer
Terraform Test Framework
The native test framework runs inside Terraform itself. Test files (.tftest.hcl) define run blocks that create temporary infrastructure, assert conditions, and automatically clean up. Each run block can use command = plan (no resources created) or command = apply (creates real resources in an ephemeral workspace).
Terraform Test vs Terratest
- Terraform test: HCL-based, no Go required, runs inside terraform, limited to assertions on plan/state/outputs. Best for module authors validating contract. - Terratest: Go-based, can make HTTP calls, SSH into instances, run arbitrary validation. Best for integration testing (e.g., verify the web server actually responds on port 443). - Recommendation: Use terraform test for module contract tests, Terratest for end-to-end infrastructure validation.
Testing Pyramid for IaC
1. Static analysis (fastest): terraform validate, tflint, checkov, tfsec 2. Plan-time tests: terraform test with command = plan - verify resource counts, naming, tagging 3. Apply-time tests: terraform test with command = apply - verify actual outputs, dependencies 4. Integration tests: Terratest - verify network connectivity, security group rules, routing 5. Policy tests: OPA/Sentinel against plan JSON - verify compliance
VPC Module Testing Strategy
Test the module provisions the correct number of subnets, CIDR ranges don't overlap, route tables are correctly associated, NAT gateway is in public subnet, and outputs contain expected values. Use variable overrides in test to exercise different configurations (2 AZs vs 3 AZs, with/without NAT gateway).
Code Example
# tests/vpc_basic.tftest.hcl
run "validate_vpc_creation" {
command = plan
variables {
vpc_cidr = "10.0.0.0/16"
environment = "test"
azs = ["us-east-1a", "us-east-1b"]
}
assert {
condition = aws_vpc.main.cidr_block == "10.0.0.0/16"
error_message = "VPC CIDR does not match expected value"
}
assert {
condition = length(aws_subnet.private) == 2
error_message = "Expected 2 private subnets for 2 AZs"
}
assert {
condition = length(aws_subnet.public) == 2
error_message = "Expected 2 public subnets for 2 AZs"
}
}
run "verify_nat_gateway" {
command = plan
variables {
vpc_cidr = "10.0.0.0/16"
environment = "test"
azs = ["us-east-1a", "us-east-1b"]
enable_nat_gw = true
}
assert {
condition = aws_nat_gateway.main[0].subnet_id == aws_subnet.public[0].id
error_message = "NAT Gateway must be in a public subnet"
}
}
run "full_apply_test" {
command = apply
variables {
vpc_cidr = "10.99.0.0/16" # Use non-overlapping CIDR for test
environment = "tftest"
azs = ["us-east-1a"]
}
assert {
condition = output.vpc_id != ""
error_message = "VPC ID output must not be empty"
}
assert {
condition = length(output.private_subnet_ids) > 0
error_message = "Must output at least one private subnet ID"
}
}
# Run tests
terraform test -verbose
# Terratest equivalent (Go)
# func TestVPCModule(t *testing.T) {
# terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
# TerraformDir: "../",
# Vars: map[string]interface{}{"vpc_cidr": "10.99.0.0/16", "environment": "test"},
# })
# defer terraform.Destroy(t, terraformOptions)
# terraform.InitAndApply(t, terraformOptions)
# vpcID := terraform.Output(t, terraformOptions, "vpc_id")
# assert.NotEmpty(t, vpcID)
# }Interview Tip
Testing IaC is a differentiator in interviews. Most candidates only mention terraform validate. Show you know the testing pyramid: static analysis -> plan assertions -> apply tests -> integration tests -> policy checks. The native test framework is relatively new, so knowing it signals you stay current.