2026-06-18 · 7 min read
Testing Terraform Modules with Terratest (and Cheaper Alternatives)
How to actually test your Terraform, from free static checks (validate, tflint, tfsec) to real integration tests with Terratest, so your modules don't break production.

Testing Terraform Modules with Terratest (and Cheaper Alternatives)
"It applied, so it works" is how Terraform modules quietly rot. A module that worked last quarter breaks when a provider updates, a default changes, or someone refactors. Testing catches that. Here's the layered approach I use, cheap checks first, real tests where they earn their keep.
Layer 1: Static checks (free, fast, run on every PR)
Most regressions are caught here, in seconds, with no cloud cost:
terraform fmt -check -recursive # formatting
terraform validate # syntax + internal consistency
tflint # provider-specific lint, deprecated args
tfsec # or: trivy config . # security misconfig (open SGs, no encryption)
Wire these into CI and they gate every change. For a lot of modules, this layer is 80% of the value.
Layer 2: Native tests (terraform test)
Terraform 1.6+ ships a built-in test framework, HCL test files, no extra language:
# tests/defaults.tftest.hcl
run "uses_safe_defaults" {
command = plan
assert {
condition = aws_s3_bucket_public_access_block.this.block_public_acls == true
error_message = "Bucket must block public ACLs by default"
}
}
plan-based tests are fast and free (no real resources). Great for asserting your module's logic and
defaults.
Layer 3: Terratest (real integration tests)
When you need to prove a module actually provisions working infrastructure, Terratest (Go) applies real resources, asserts against them, and tears them down:
func TestVpcModule(t *testing.T) {
opts := &terraform.Options{TerraformDir: "../examples/basic"}
defer terraform.Destroy(t, opts) // always clean up
terraform.InitAndApply(t, opts)
vpcID := terraform.Output(t, opts, "vpc_id")
assert.NotEmpty(t, vpcID)
// e.g. use the AWS SDK to confirm the VPC really exists and is configured right
}
Terratest is powerful but it costs money and time (it spins up real resources), so reserve it for modules where "does it really work end to end" matters.
What to use when
| Concern | Tool | Cost |
|---|---|---|
| Formatting / syntax | fmt, validate | free |
| Lint / deprecations | tflint | free |
| Security misconfig | tfsec / checkov | free |
| Logic / defaults | terraform test (plan) | free |
| Real provisioning | Terratest / terraform test (apply) | $ + time |
Practical advice
- Start at the top of the table. Most teams get huge value from just the free layers in CI and never need much Terratest.
- Test examples, not the module directly. Keep
examples/dirs that consume your module; test those. They double as documentation. - Always
deferdestroy in Terratest, and run integration tests in a sandbox account with budget alerts. - Pin provider versions so a provider release doesn't silently change behaviour mid-test.
My terraform-aws-reference-architecture runs the free layers in CI on every push, the cheapest, highest-leverage testing you can add today.
Need your Terraform made reliable and testable? That's my consulting work, get in touch.