Stage 4 · Provision
Terraform Fundamentals
Testing Terraform
Terratest, kitchen-terraform, and plan validation in CI — verifying infrastructure before it reaches production.
Why Test Terraform?
Terraform configurations can break in subtle ways. A variable validation rule might be too permissive. A module update might change an output format. A provider upgrade might deprecate an argument. Without tests, you find these bugs in production.
Testing Terraform is different from testing application code. You are verifying that infrastructure provisions correctly, behaves as expected, and can be torn down cleanly. This requires deploying real resources in most cases.
Types of Tests
| Test Type | What It Validates | Speed | Cost |
|---|---|---|---|
| terraform validate | Syntax and type errors | Instant | Free |
| terraform plan -detailed-exitcode | Changes match expectations | Seconds | Free |
| TFLint / Checkov | Best practices and policies | Seconds | Free |
| Terratest | Real infrastructure works | Minutes | Costs money |
Terratest Basics
Terratest is a Go library by Gruntwork that provisions real infrastructure, validates it, and tears it down. It is the industry standard for Terraform module testing.
Plan Validation in CI
You can validate Terraform plans without applying them. This is fast, free, and catches most issues. The -detailed-exitcode flag returns exit code 2 when the plan includes changes, 0 when no changes, and 1 on error.
#!/bin/bash
terraform init -backend=false
terraform validate
terraform plan -out=tfplan -detailed-exitcode
EXIT_CODE=$?
if [ $EXIT_CODE -eq 1 ]; then
echo "Plan failed"
exit 1
fi
if [ $EXIT_CODE -eq 2 ]; then
echo "Plan includes changes"
# Run policy checks against tfplan
fiThe -backend=false flag skips backend initialization — useful when the backend is not configured in CI. The plan file (tfplan) can be consumed by policy engines, cost estimation tools, or stored as a CI artifact.
The Fixture Pattern
A test fixture is a minimal Terraform configuration that calls the module under test. It provides realistic inputs without the complexity of a full production configuration.
module "vpc" {
source = "../../../modules/vpc"
cidr_block = "10.0.0.0/16"
enable_nat_gateway = true
}
output "vpc_id" {
value = module.vpc.vpc_id
}
output "private_subnet_ids" {
value = module.vpc.private_subnet_ids
}The fixture module calls the real module with test-appropriate values. Terratest provisions this fixture and validates its outputs. Each test can have its own fixture with different configurations.
CI Integration
Integrate Terraform tests into your CI pipeline to catch issues before merge. The pipeline should validate, lint, plan, and optionally run Terratest for critical modules.
name: Terraform CI
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Format
run: terraform fmt -check
- name: Terraform Init
run: terraform init -backend=false
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan -detailed-exitcode
- name: TFLint
run: tflint --config=.tflint.hclThe -backend=false flag is essential for CI where credentials for the remote backend may not be available. Each step runs independently, so you see all failures at once rather than stopping at the first one.
Never test Terraform in a shared environment. Use a dedicated test account with minimal permissions and automatic cleanup. Terratest handles cleanup via defer, but CI pipelines should also have a timeout-based cleanup job.
The base is fast, free checks (validate, lint, plan). The middle is policy enforcement (OPA, Sentinel). The top is Terratest for critical modules. Most teams should invest heavily in the base and middle layers.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.