Skip to content

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 infrastructure

Each layer answers a different question.

What Does Each Terraform Check Tell You?

terraform fmt

Formatting is the cheapest check.

terraform fmt -check

It answers:

Is the Terraform configuration formatted according to Terraform’s formatting rules?

Without -check, Terraform can rewrite formatting:

terraform fmt

Formatting does not prove that the configuration is valid or correct. It simply removes an entire class of avoidable inconsistencies.

terraform validate

Initialize first:

terraform init

Then:

terraform validate

Validation 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 plan

A 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:

CheckMain question
terraform fmtIs the configuration consistently formatted?
terraform validateDoes the configuration make structural/configuration sense?
terraform planWhat 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.hcl

Let’s build a small self-contained module.

The Example Module

Create:

terraform-test-example/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
    └── basic.tftest.hcl

main.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 test

The 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 test

The 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 verification

Native 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 plan

The 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
      ↓
merge

The 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.

Last updated on