Testing and Validation
Terraform confidence should be built in layers. You do not need to create infrastructure every time you want to discover a typo, and you should not treat a successful terraform plan as proof that the resulting application will behave correctly.
A useful progression is:
Format
↓
Validate
↓
Plan
↓
Test
↓
Policy / Security checks
↓
Apply
↓
Verify real infrastructureEach layer answers a different question.
What Does Each Terraform Check Tell You?
terraform fmt
Formatting is the cheapest check.
terraform fmt -checkIt answers:
Is the Terraform configuration formatted according to Terraform’s formatting rules?
Without -check, Terraform can rewrite formatting:
terraform fmtFormatting does not prove that the configuration is valid or correct. It simply removes an entire class of avoidable inconsistencies.
terraform validate
Initialize first:
terraform initThen:
terraform validateValidation checks whether the configuration is structurally valid for Terraform and the initialized providers.
It can catch things such as:
- malformed configuration
- invalid argument structure
- type-related configuration problems
- references Terraform cannot resolve
- invalid resource arguments according to provider schemas
It does not ask the cloud provider to create anything.
terraform plan
A plan answers a more important question:
Given the current configuration, state, and provider information, what does Terraform intend to change?
Run:
terraform planA plan can reveal:
- resources to create
- resources to update
- resources to destroy
- replacements
- unexpected values
- dependency-driven changes
That makes plan review one of the most important safety steps before production changes.
The three commands are therefore different:
| Check | Main question |
|---|---|
terraform fmt | Is the configuration consistently formatted? |
terraform validate | Does the configuration make structural/configuration sense? |
terraform plan | What infrastructure change does Terraform intend to make? |
A successful result at one layer does not imply success at the next.
Native Terraform Tests
Terraform also provides native testing through terraform test.
This is especially useful for reusable modules. Instead of applying your entire production infrastructure, you can exercise a module and assert expected behavior.
A test file uses the .tftest.hcl extension.
A minimal structure looks like:
module/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
└── basic.tftest.hclLet’s build a small self-contained module.
The Example Module
Create:
terraform-test-example/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
└── basic.tftest.hclmain.tf:
terraform {
required_version = ">= 1.6.0"
}
resource "terraform_data" "example" {
input = var.name
}variables.tf:
variable "name" {
type = string
description = "Name stored by the example resource."
validation {
condition = length(trimspace(var.name)) > 0
error_message = "name must not be empty."
}
}outputs.tf:
output "name" {
value = terraform_data.example.input
}Now create tests/basic.tftest.hcl:
run "valid_name" {
command = plan
variables {
name = "devstuffs"
}
assert {
condition = output.name == "devstuffs"
error_message = "The module should return the supplied name."
}
}Run:
terraform init
terraform testThe test uses a plan rather than creating real infrastructure.
The important idea is that the test can inspect Terraform’s planned behavior and assert something about it.
Testing Different Behaviors
A module should not only test its happy path.
The variable validation above intentionally rejects an empty value.
You can add a negative test:
run "empty_name_rejected" {
command = plan
variables {
name = ""
}
expect_failures = [
var.name
]
}Now run:
terraform testThe test expects validation to fail for that variable.
This is useful because validation rules are behavior too. A test can protect them against accidental removal during refactoring.
Note
The exact testing features available depend on the Terraform version you use. Keep the required Terraform version explicit in projects so the behavior of your test suite is reproducible.
What Can terraform test Prove?
Native tests are valuable, but they have boundaries.
A module test can give you confidence that:
- variables are accepted or rejected correctly
- planned resources have expected arguments
- outputs behave as expected
- module logic produces the intended plan
- certain resource relationships exist
It does not automatically prove that:
- a cloud provider is healthy
- IAM permissions are correct in every real scenario
- an application is reachable
- a database accepts connections
- a real network behaves as intended
- the deployed application meets business requirements
That distinction is critical.
Think in three questions:
Does the Terraform configuration make sense?
│
└── fmt + validate
Does the planned infrastructure match what I intended?
│
└── plan + tests
Does the resulting infrastructure actually behave correctly?
│
└── integration / operational verificationNative Terraform testing is strongest in the middle layer.
A Practical Validation Pipeline
A small CI validation sequence might be:
terraform fmt -check
terraform init
terraform validate
terraform test
terraform planThe order is deliberate.
Formatting is cheap. Validation should happen before spending effort reviewing a plan. Tests can catch module behavior that syntax validation cannot. The plan then shows the concrete infrastructure change.
For a pull request, the workflow can look like:
Developer change
↓
fmt
↓
validate
↓
terraform test
↓
plan
↓
human review
↓
mergeThe exact CI implementation is less important than preserving these distinct checks.
Static, Policy, And Security Checks
Larger organizations often add checks outside Terraform itself.
Conceptually, these layers might inspect:
- configuration quality
- insecure resource settings
- organizational policies
- prohibited infrastructure patterns
- dependency versions
These checks are useful because Terraform’s own validation answers only Terraform-specific questions.
For example:
terraform validate
↓
"Is this valid Terraform?"
policy check
↓
"Is this allowed by our organization?"
security check
↓
"Does this contain known security risks?"You do not need a large collection of third-party tools to benefit from the concept. The important architectural lesson is to place different checks at the layer where they can answer the question reliably.
Testing Before Merging
A mature Terraform workflow treats tests and plans as part of change review.
Suppose a developer changes:
instance_type = "t3.micro"to:
instance_type = "m7i.4xlarge"Terraform may happily produce a valid plan. terraform validate cannot tell you that the new instance is unexpectedly expensive.
A plan reviewer can.
Likewise, a module test can catch an accidental change in a resource argument, while a policy check might reject an infrastructure pattern that the module technically supports.
Confidence comes from combining layers rather than asking one command to prove everything.