Testing Terraform
A bad Terraform change can delete a database, open a security group to the internet, or take down networking for every service at once. Infrastructure code deserves at least as much verification as application code, and the tooling has matured. Formatters and validators catch syntax problems, linters catch provider-specific mistakes, policy engines enforce security and compliance rules, and the native terraform test framework runs real assertions against modules, with or without creating real resources.
The goal is a layered pipeline: fast static checks on every commit, plan-level checks on every pull request, and real integration tests for shared modules.
TL;DR
terraform fmt -checkandterraform validateare the free baseline in every pipeline.- TFLint catches provider-specific errors (invalid instance types, deprecated syntax) that
validatemisses. - Security and policy scanners (Checkov, Trivy, OPA/Conftest, Sentinel) enforce rules like "no public buckets" on code or plans.
terraform testruns.tftest.hclfiles withrunblocks andassertconditions, using eithercommand = plan(no resources) orapply(real ones).- Mock providers (1.7+) let module tests run without cloud credentials.
- Review the plan in the pull request before anything is applied; it's the most important check of all.
Quick Example
A test file for the secure-bucket module from Terraform modules:
Core Concepts
The Testing Pyramid for IaC
terraform test
Test files (*.tftest.hcl, in the module root or tests/) contain:
runblocks, each executingplanorapplyagainst the module, with optional variable overrides.assertblocks with conditions that can reference resources, outputs, and variables.expect_failuresfor tests asserting that validations or preconditions reject bad input.mock_providerandoverride_resource/override_datato fake provider responses.
With command = apply, Terraform creates real resources for the test and destroys them afterwards, in reverse order. Run blocks in one file share state sequentially, so later runs can build on earlier ones. A module block inside a run can point to a setup helper module that creates prerequisites.
Policy as Code
Policies codify rules that reviewers would otherwise have to remember:
Evaluating policies against the plan JSON, rather than the source, catches issues that only appear after variables and modules are resolved. HCP Terraform runs Sentinel or OPA policies natively as a gate before apply.
Terratest
Terratest is a Go library that runs terraform apply, then makes real assertions: an HTTP request to the deployed load balancer, an SSH check, a query to the created database. It then destroys everything. It's ideal for high-value shared modules where "the plan looks right" isn't enough and you need to prove the infrastructure works.
A Practical CI Pipeline
Tools like Atlantis, HCP Terraform, Spacelift, and env0 implement this plan-in-PR, apply-on-approval workflow. See CI/CD and GitHub Actions.
Best Practices
Always Review the Plan
The plan is the single most valuable test: it shows exactly what will be created, changed, and destroyed. Surface it in pull requests and require explicit attention to any destroy or replace. Some teams fail the pipeline automatically when a plan destroys stateful resources.
Test Modules, Not Every Root Configuration
Shared modules are reused in many places, so they deserve unit and integration tests. Root configurations are mostly wiring, where linting, policy checks, and plan review give good coverage.
Use Ephemeral Test Accounts
Integration tests that apply real resources should run in dedicated sandbox accounts or projects, with unique name prefixes per run and scheduled cleanup (such as aws-nuke or cloud-nuke) for anything a failed test leaves behind.
Add Preconditions and Postconditions
lifecycle { precondition { ... } } and postcondition blocks turn assumptions into checks that run on every plan and apply. They catch problems in production runs, not just in tests.
Common Mistakes
Only Running validate
terraform validate checks syntax and internal consistency, but not whether t3.mega is a real instance type or whether a bucket is public. Add TFLint and a security scanner. Both are fast and cheap.
Applying a Different Plan Than Was Reviewed
Re-running plan at apply time can pick up changes merged since the review. Save the reviewed plan (-out=plan.out) and apply that file, or require a fresh approval if it's stale.
Integration Tests Without Cleanup
A test that fails mid-apply may never reach its destroy step, leaving orphaned resources that cost money and collide with the next run's names. Use unique names, t.Cleanup/defer in Terratest, and scheduled sweeper jobs.
FAQ
Is terraform test a replacement for Terratest?
For many modules, yes. It's native, written in HCL, supports mocks, and handles cleanup. Terratest remains useful when you need rich assertions beyond Terraform's view: making HTTP calls, checking DNS, SSHing into instances, or orchestrating multi-step scenarios in Go.
Can I test Terraform without cloud credentials?
Yes. Static tools (fmt, validate, TFLint, Checkov) need none, and terraform test with mock_provider runs plans against fake providers. Anything using command = apply with real providers needs credentials and creates real resources.
Should policy checks block merges?
Critical rules, such as no public data stores, encryption required, or no wildcard IAM, should block. Advisory rules (tag conventions, cost warnings) can warn at first and be tightened later. Allow documented, reviewable exceptions so teams aren't forced into workarounds.
How do I test for drift?
Run a scheduled terraform plan -detailed-exitcode against each environment. Exit code 2 means changes are pending, meaning someone modified infrastructure outside Terraform or code was merged but not applied. Alert on it. See Terraform state.
Related Topics
- Terraform — The tool overview
- Terraform Modules — What you're usually testing
- Terraform State — Drift detection and safe applies
- CI/CD — Where these checks run
- Integration Testing — Testing against real systems
- Infrastructure as Code — Treating infrastructure like software