Terraform Test
Terraform's built-in testing framework validates that configuration updates work correctly. Tests execute against temporary resources, protecting existing infrastructure and state.
File Structure
Test files use .tftest.hcl extension, organized in a tests/ directory. Name files to distinguish unit vs integration tests:
my-module/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
├── validation_unit_test.tftest.hcl
├── edge_cases_unit_test.tftest.hcl
└── full_stack_integration_test.tftest.hcl
A test file contains:
- Zero to one
testblock (settings) - One or more
runblocks (test executions) - Zero to one
variablesblock (input values) - Zero or more
provider/mock_providerblocks
Test Block
Optional test-wide settings (since v1.6.0):
test {
parallel = true # Enable parallel run blocks (default: false)
}
Run Block
Each run block executes one test scenario. Run blocks execute sequentially by default.
run "test_default_configuration" {
command = plan # "apply" (default) or "plan"
assert {
condition = aws_instance.example.instance_type == "t2.micro"
error_message = "Instance type should be t2.micro by default"
}
}
Attributes:
| Attribute | Description |
|---|---|
command |
apply (default) or plan |
plan_options |
Plan behavior config (mode, refresh, replace, target) |
variables |
Override test-level variable values |
module |
Reference alternate modules |
providers |
Customize provider availability |
assert |
Validation conditions (multiple allowed) |
expect_failures |
Expected validation failures |
state_key |
State file isolation (since v1.9.0) |
parallel |
Enable parallel execution (since v1.9.0) |
Plan Options
run "test_refresh_only" {
command = plan
plan_options {
mode = refresh-only # "normal" (default) or "refresh-only"
refresh = true
replace = [aws_instance.example]
target = [aws_instance.example]
}
}
Variables
Define at file level (all run blocks) or within individual run blocks. Test file variables take the highest precedence, overriding env vars, .tfvars, and CLI input.
# File-level
variables {
aws_region = "us-west-2"
instance_type = "t2.micro"
}
# Run-level override
run "test_override" {
command = plan
variables {
instance_type = "t3.large"
}
}
# Reference prior run outputs
run "setup_vpc" {
command = apply
}
run "use_vpc" {
command = plan
variables {
vpc_id = run.setup_vpc.vpc_id
}
}
Assert Block
All assertions within a run block must pass for the test to succeed:
assert {
condition = <expression>
error_message = "failure description"
}
Supports resource attributes, outputs, run.<name>.<output> references, and complex expressions like alltrue(), can(regex(...)), length().
Expect Failures
Test that invalid input is properly rejected. The test passes if the listed checkable objects report an error:
run "test_invalid_input_rejected" {
command = plan
variables {
instance_count = -1
}
expect_failures = [
var.instance_count
]
}
Checkable objects: input variables, output values, check blocks, resources, data sources.
Module Block
Test a specific module rather than root configuration. Supports local paths and registry modules (Git/HTTP sources are not supported).
run "test_vpc_module" {
command = plan
module {
source = "./modules/vpc" # local
# source = "hashicorp/vpc/aws" # registry
# version = "5.0.0" # registry only
}
variables {
cidr_block = "10.0.0.0/16"
}
}
Provider Configuration
Override providers for tests. Since v1.7.0, provider blocks can reference test variables.
provider "aws" {
region = "us-west-2"
}
provider "aws" {
alias = "secondary"
region = "us-east-1"
}
run "test_with_secondary" {
command = plan
providers = {
aws = provider.aws.secondary
}
}
State Key
Controls which state file a run block uses. By default, main config shares state across all run blocks; each alternate module gets its own state.
run "create_vpc" {
command = apply
module { source = "./modules/vpc" }
state_key = "shared_state"
}
run "create_subnet" {
command = apply
module { source = "./modules/subnet" }
state_key = "shared_state" # shares state with create_vpc
}
Parallel Execution
Enable with parallel = true (since v1.9.0). Requirements:
- No inter-run output references between parallel blocks
- Different state files (via different modules or state keys)
run "test_module_a" {
command = plan
parallel = true
module { source = "./modules/module-a" }
}
run "test_module_b" {
command = plan
parallel = true
module { source = "./modules/module-b" }
}
# Non-parallel block creates a synchronization point
run "test_integration" {
command = plan
}
Mock Providers
Simulate provider behavior without creating real infrastructure (since v1.7.0). Mocks only work with command = plan.
mock_provider "aws" {
mock_resource "aws_instance" {
defaults = {
id = "i-1234567890abcdef0"
instance_type = "t2.micro"
ami = "ami-12345678"
}
}
mock_data "aws_ami" {
defaults = {
id = "ami-12345678"
}
}
}
run "test_with_mocks" {
command = plan
assert {
condition = aws_instance.example.id == "i-1234567890abcdef0"
error_message = "Mock instance ID should match"
}
}
Use mocks for: testing logic/conditionals, local dev without cloud access, fast CI feedback.
See references/examples.md for comprehensive mock provider definitions.
Cleanup
Resources are destroyed in reverse run block order after test completion — important for dependency ordering. Use terraform test -no-cleanup for debugging.
Test Execution
terraform test # all tests
terraform test tests/defaults.tftest.hcl # specific file
terraform test -verbose # detailed output
terraform test -test-directory=integration-tests # custom directory
terraform test -filter=test_vpc_configuration # filter by name
terraform test -no-cleanup # keep resources for debugging
Best Practices
- Naming:
*_unit_test.tftest.hclfor plan-mode,*_integration_test.tftest.hclfor apply-mode - Plan mode first: use
command = planfor fast, cost-free validation; reserveapplyfor integration tests - Mock providers: isolate unit tests from cloud dependencies (v1.7.0+)
- Clear error messages: write assertion messages that diagnose failures without requiring investigation
- Variable coverage: test different combinations to validate all code paths
- Negative testing: use
expect_failuresfor invalid inputs - Parallel execution: use
parallel = truefor independent tests with separate state - CI integration: run
terraform testin CI pipelines to catch regressions
References
references/test-patterns.md— common unit and integration test patterns, advanced featuresreferences/examples.md— complete test suites, mock provider templates, CI/CD configsreferences/troubleshooting.md— common issues and solutions- Terraform Testing Documentation
- Terraform Test Command Reference