| name | terraform-test |
| description | Guide for writing Terraform tests (.tftest.hcl). Covers run blocks, assertions, mock providers, test modes, and CI integration. |
| metadata | {"copyright":"Copyright IBM Corp. 2026","version":"0.0.1"} |
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
test block (settings)
- One or more
run blocks (test executions)
- Zero to one
variables block (input values)
- Zero or more
provider / mock_provider blocks
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
terraform test tests/defaults.tftest.hcl
terraform test -verbose
terraform test -test-directory=integration-tests
terraform test -filter=test_vpc_configuration
terraform test -no-cleanup
Best Practices
- Naming:
*_unit_test.tftest.hcl for plan-mode, *_integration_test.tftest.hcl for apply-mode
- Plan mode first: use
command = plan for fast, cost-free validation; reserve apply for 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_failures for invalid inputs
- Parallel execution: use
parallel = true for independent tests with separate state
- CI integration: run
terraform test in CI pipelines to catch regressions
References