Testing Terraform Configurations: Validate, TFLint, TFSec, Terratest & Unit Tests
In this tutorial, you'll learn about Testing Terraform Configurations: Validate, TFLint, TFSec, Terratest & Unit Tests. We cover key concepts, practical examples, and best practices.
Testing Terraform configurations combines static analysis, security scanning, unit tests, and integration tests to catch errors, enforce policies, and validate infrastructure behavior before deployment.
What You'll Learn
In this tutorial, you will learn how to implement a comprehensive Terraform testing strategy using <a href="/devops/terraform/">terraform</a> validate, <a href="/devops/terraform/">terraform</a> fmt, TFLint, TFSec, the built-in <a href="/devops/terraform/">terraform</a> test framework, and Terratest for full integration testing.
Why It Matters
Untested Terraform configurations are the leading cause of infrastructure incidents. A missing validation catches a bad CIDR block only after an apply fails. Security scanning catches public S3 buckets before data exposure. Integration tests verify that resources actually work together.
Real-World Use
DodaTech runs a five-stage Terraform testing pipeline: fmt, validate, tflint, tfsec, and terraform test. Durga Antivirus Pro's CI pipeline rejects any change that fails security scanning or unit tests, preventing misconfigured security groups and unencrypted storage from reaching production.
Testing Pipeline Overview
graph LR
A[Code Change] --> B[terraform fmt -check]
B --> C[terraform validate]
C --> D[tflint]
D --> E[tfsec]
E --> F[terraform test]
F --> G[Terratest]
G --> H[terraform plan]
H --> I[terraform apply]
B -.-> J{Pass?}
J -->|No| K[Block PR]
J -->|Yes| C
F -.-> L{Pass?}
L -->|No| M[Block Deploy]
L -->|Yes| G
style A fill:#4a90d9,color:#fff
style K fill:#e74c3c,color:#fff
style H fill:#50c878,color:#fff
Static Analysis Testing
Terraform Fmt
# Check formatting in CI
terraform fmt -check -recursive
Expected output (when unformatted files exist):
main.tf
modules/vpc/main.tf
Expected output (when all formatted):
# No output, exit code 0
Terraform Validate
# Intentional error: missing required provider
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
terraform init && terraform validate
Expected output (with error):
Error: Reference to undeclared resource
on main.tf line 1:
resource "aws_instance" "web" has not been declared in the root module
TFLint
# Configuration with linter warnings
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
tflint --init && tflint
Expected output:
1 issue(s) found:
Warning: Missing version constraint for provider "aws" (terraform_required_providers)
on main.tf line 1:
Source: https://github.com/terraform-linters/tflint-ruleset-aws
TFSec Security Scanning
# Insecure configuration
resource "aws_s3_bucket" "data" {
bucket = "company-data"
acl = "public-read"
}
resource "aws_security_group" "ssh" {
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
tfsec .
Expected output:
Results: 2 failed, 28 passed
aws_s3_bucket.data [AWS001] - S3 bucket has public read ACL.
aws_security_group.ssh [AWS011] - Security group rule allows ingress from 0.0.0.0/0 on port 22.
Unit Testing with Terraform Test Framework
Writing Unit Tests
# tests/instance_test.tftest.hcl
provider "aws" {
region = "us-east-1"
}
run "validate_instance_type" {
command = plan
assert {
condition = aws_instance.web.instance_type == "t3.medium"
error_message = "Instance type should be t3.medium"
}
}
run "validate_security_group_count" {
command = plan
assert {
condition = length(aws_instance.web.vpc_security_group_ids) >= 2
error_message = "Web instance must have at least 2 security groups"
}
}
run "validate_encryption" {
command = plan
assert {
condition = aws_ebs_volume.data.encrypted
error_message = "Data volume must be encrypted"
}
}
run "validate_tags" {
command = plan
assert {
condition = lookup(aws_instance.web.tags, "Environment", "") == "testing"
error_message = "Environment tag must be set to testing"
}
assert {
condition = lookup(aws_instance.web.tags, "ManagedBy", "") == "Terraform"
error_message = "ManagedBy tag must be set to Terraform"
}
}
terraform test
Expected output:
tests/instance_test.tftest.hcl... in progress
run "validate_instance_type"... pass
run "validate_security_group_count"... pass
run "validate_encryption"... pass
run "validate_tags"... pass
tests/instance_test.tftest.hcl... tearing down
tests/instance_test.tftest.hcl... pass
Success! 4 passed, 0 failed.
Testing with Variables
# tests/production_test.tftest.hcl
variables {
environment = "production"
instance_type = "t3.large"
instance_count = 5
}
run "validate_production_config" {
command = plan
assert {
condition = var.instance_count >= 3
error_message = "Production must have at least 3 instances"
}
assert {
condition = aws_db_instance.main.multi_az
error_message = "Production database must be Multi-AZ"
}
}
Integration Testing with Terratest
Go-Based Terratest
// test/integration_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/stretchr/testify/assert"
)
func TestWebServerInfrastructure(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/web-server",
Vars: map[string]interface{}{
"environment": "test",
"instance_type": "t3.micro",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
instanceID := terraform.Output(t, terraformOptions, "instance_id")
publicIP := terraform.Output(t, terraformOptions, "public_ip")
// Verify instance is running
status := aws.GetInstanceState(t, "us-east-1", instanceID)
assert.Equal(t, "running", status)
// Verify HTTP endpoint responds
response := aws.HTTPGet(t, "http://"+publicIP)
assert.Contains(t, response, "Hello from Terraform")
}
go test -v -timeout 60m ./test/
Expected output:
=== RUN TestWebServerInfrastructure
TestWebServerInfrastructure: Running terraform init
TestWebServerInfrastructure: Running terraform apply
TestWebServerInfrastructure: Verifying instance is running
TestWebServerInfrastructure: Checking HTTP response
TestWebServerInfrastructure: Running terraform destroy
--- PASS: TestWebServerInfrastructure (185.23s)
PASS
CI Pipeline Integration
# .github/workflows/terraform-test.yml
name: Terraform Tests
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Format Check
run: terraform fmt -check -recursive
- name: Init and Validate
run: |
terraform init
terraform validate
- name: Unit Tests
run: terraform test
- name: Security Scan
uses: aquasecurity/tfsec-action@v1
Common Mistakes
1. Only Running Validate
<a href="/devops/terraform/">terraform</a> validate checks syntax only. It does not catch security issues, deprecated syntax, or provider-specific errors.
2. Skipping Fmt Check
Without <a href="/devops/terraform/">terraform</a> fmt -check in CI, formatting inconsistencies accumulate and make code review harder.
3. Not Scanning Secrets
TFSec does not detect hardcoded passwords in configuration. Use additional tools like trufflehog.
4. Writing Tests After Deployment
Test-driven infrastructure catches issues before they reach production. Write tests before apply, not after.
5. Ignoring Terratest for Complex Logic
Unit tests verify plan output only. Terratest provisions real resources and validates actual behavior.
Practice Questions
1. What is the difference between <a href="/devops/terraform/">terraform</a> validate and tflint?
Validate checks Terraform core syntax. TFLint checks provider-specific rules, best practices, and deprecated syntax.
2. How do you write a unit test that verifies an EBS volume is encrypted?
Use a <a href="/devops/terraform/">terraform</a> test file with command = plan and an assert block checking aws_ebs_volume.data.encrypted == true.
3. What does Terratest provide that <a href="/devops/terraform/">terraform</a> test does not?
Terratest provisions real infrastructure and validates actual resource behavior (HTTP responses, instance states, database connections).
4. Challenge: Write a .tftest.hcl file that validates a deployment must have: at least 3 instances in production, Multi-AZ database, all EBS volumes encrypted, and no security groups open to 0.0.0.0/0 on port 22.
Mini Project: Complete Testing Pipeline
Set up a five-stage testing pipeline: terraform fmt -check, terraform validate, tflint, tfsec, and terraform test. Write unit tests for instance type, tag enforcement, and encryption requirements. Add a Terratest that provisions a web server and verifies the HTTP response.
Related Concepts
What's Next
Implement Terraform testing in your CI/CD pipeline, then study Best Practices for production-grade infrastructure automation. Explore DevOps testing strategies for infrastructure reliability.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro