CoursesInfrastructure as Code & automationAdvanced · Testing & secrets

Testing IaC: Terratest & InSpec

Prove infrastructure works before prod.

Advanced12 min · lesson 21 of 23

Infrastructure code deserves testing as much as application code — a bad module or a broken change can take down production just as surely as a bug. IaC testing spans a spectrum: static checks (validate, format, lint, and the scanners you already met) catch syntax and known issues cheaply; then plan-based tests assert what a plan would do; and finally integration tests actually create real infrastructure in a sandbox, verify it works, and tear it down. The higher you go, the more confidence and the more cost — so you use each level for what it is good at.

The IaC testing spectrum
cheap & fast (run always)
fmt / validate
syntax, formatting
tflint
lint: best practices, errors
scanners + policy
security, org rules
real but costly (key modules)
Terratest
apply for real, assert, destroy
InSpec
verify the built infra’s state
Run the cheap layer on every change; reserve real apply-and-verify tests for important modules and releases.

Terratest: real infrastructure, verified

Terratest is the standard for integration-testing IaC: a Go library that programmatically runs terraform apply in a sandbox account, makes assertions against the real resources (the server responds, the bucket has the right policy, the output is correct), and then runs destroy to clean up — even if the test fails. This is the strongest confidence you can get: you proved the module actually builds working infrastructure, not just that it parses. Because it creates real resources, it costs money and time, so you run it for important reusable modules and before releases, not on every tiny change.

network_test.go (Terratest)
func TestNetworkModule(t *testing.T) {
opts := &terraform.Options{ TerraformDir: "../modules/network" }
defer terraform.Destroy(t, opts) // always tear down, even on failure
terraform.InitAndApply(t, opts) // actually build it in a sandbox
subnetID := terraform.Output(t, opts, "private_subnet_id")
assert.NotEmpty(t, subnetID) // verify the module produced a real subnet
}

InSpec and the pragmatic mix

InSpec is a compliance-and-testing framework that verifies the state of infrastructure against a described spec — "port 22 should not be open to the world," "this service should be running" — readable enough to double as executable compliance documentation. In practice, most teams lean heavily on the cheap layer (fmt, validate, tflint, scanners, policy) which runs on every change, add Terratest for their critical shared modules, and use InSpec-style checks for compliance verification. You do not need every layer everywhere; you need the cheap checks always and real tests where the blast radius justifies the cost.

Test infrastructure destroys itself — and must, in a sandbox
Real IaC tests (Terratest) create actual cloud resources that cost money and, if not cleaned up, accumulate as orphaned infrastructure and surprise bills — always use defer Destroy so teardown runs even when a test fails, and run these tests in a dedicated sandbox account, never against production. And ensure the test credentials are scoped to that sandbox only. The point of testing infrastructure is confidence without risk; a test that leaves resources behind, or worse runs against a real environment, undermines both. Cheap static checks everywhere, real tests safely sandboxed.