Infrastructure modules have contracts — "given these variables, produce resources with these properties" — but most teams never write the tests, because test scaffolding in Terratest or terraform test is tedious boilerplate. That is precisely the work AI does well.
Tier: Core · Workflow area: Infrastructure as Code
Tested modules matter more in the AI era, not less: module tests are what let you accept AI-drafted changes to shared modules quickly, because the contract is enforced by machinery instead of reviewer vigilance.
count/for_each/dynamic blocks driven by variables) — the part that breaks silently.Skip full test suites for single-use, trivially-simple modules — the verification chain (pb-08) already covers them.
terraform test (HCL-based, no Go needed), or Go + Terratest for apply-time testing.Extract the contract before generating tests. If you cannot state the contract, tests will encode current behavior, bugs included:
Read modules/vpc (main.tf, variables.tf, outputs.tf). Draft its contract:
1. For each variable: valid input domain and what it controls.
2. For each output: what guarantees it should hold.
3. Invariants that must hold for ALL inputs (e.g., "no subnet is public
unless enable_public_subnets = true", "NAT gateway count equals AZ
count when enable_nat = true").
4. Which input combinations are invalid and should fail validation.
Mark anything ambiguous as a QUESTION for me — do not resolve it yourself.
Review the QUESTIONs — they are usually real design gaps, and answering them is the highest-value ten minutes in this playbook.
Generate plan-time tests first (fast, free, no cloud).
Generate terraform test files (Terraform 1.8 native syntax) for the
approved contract. Use command = plan for all cases. Cover:
- Default variables: expected resource counts and key attributes.
- enable_nat = false: assert zero aws_nat_gateway planned.
- Each invalid input combination: expect_failures on the right variable.
One run block per contract clause, named after the clause.
run "nat_disabled_means_no_nat_gateways" {
command = plan
variables { enable_nat = false }
assert {
condition = length([for r in aws_nat_gateway.this : r]) == 0
error_message = "NAT gateways planned despite enable_nat = false"
}
}
Make every test fail once. Mutation-check the suite: break the module deliberately (flip a condition, hardcode a count), run terraform test, and confirm the relevant test fails with its own error message. A generated test that cannot fail is decoration — this step catches assertions against the wrong attribute or vacuous conditions, which are the signature AI test-generation failure.
Add apply-time tests only for what plan cannot prove. IAM policy effects, DNS resolution, actual connectivity — Terratest (or command = apply with cleanup) in the sandbox. Keep these few and tagged; they cost minutes and money:
cd tests/integration && go test -run TestVpcConnectivity -timeout 30m
Wire into CI with a cost-aware split. Plan-time tests on every module PR; apply-time suite nightly or on release tags. Publish the contract doc from step 1 alongside the module README — it is now enforced documentation.
Step 3 is the whole game. AI generates plausible-looking assertions cheaply; forcing each test to demonstrably fail under mutation is what converts "tests exist" into "the contract is enforced."
terraform test (shown): no new language, plan-time tests run anywhere the chain runs — the default choice since 1.6+.terraform test runs the full suite green on the unmodified module.condition = true-shaped checks and assertions on attributes the variable never affects. Mutation-checking is the antidote.Module code is usually less sensitive than state or inventories, but client modules still embed naming and topology. Options: draft contract and tests via the client's in-tenancy endpoint; or hand the assistant a sanitized copy (hashed names, placeholder CIDRs) — contracts and tests survive renaming intact. All test tooling (terraform test, Terratest, LocalStack) is OSS and runs inside the client boundary; apply-time tests target the client's designated sandbox account only, never shared dev.