BXUP Track progress & test yourself — free
DevOps / SRE / Platform · Playbook · Core · ~4 min read
DEVOPS / SRE / PLATFORM · PLAYBOOK · CORE

Generate IaC Tests from Module Contracts

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.

When to use

  • Shared modules consumed by multiple teams/stacks, where a breaking change fans out.
  • Modules with conditional logic (count/for_each/dynamic blocks driven by variables) — the part that breaks silently.
  • Before accepting AI-generated refactors (pb-12) or feature additions to a module you own.

Skip full test suites for single-use, trivially-simple modules — the verification chain (pb-08) already covers them.

Prerequisites

  • Terraform ≥ 1.6 for native terraform test (HCL-based, no Go needed), or Go + Terratest for apply-time testing.
  • A sandbox account for tests that really apply; LocalStack for the AWS subset it supports (test against real APIs before trusting green LocalStack runs).
  • The module's intended contract, written down — even as bullets.

The workflow

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

  2. 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"
      }
    }
    
  3. 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.

  4. 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
    
  5. 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.

Key Takeaway:

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

Tool variants

  • Native terraform test (shown): no new language, plan-time tests run anywhere the chain runs — the default choice since 1.6+.
  • Terratest (Go): richer assertions against real deployed infra (HTTP checks, SSH, SDK queries); AI writes competent Terratest given the contract, and the mutation-check applies identically.
  • LocalStack for CI economy: run apply-time tests against LocalStack on PRs and against a real sandbox nightly; document which resources LocalStack fakes poorly (IAM enforcement notably) so green runs are not over-trusted.

Verification

  • Validate: test files parse; terraform test runs the full suite green on the unmodified module.
  • Plan: every test passed the mutation check — provably capable of failing, with the failure message pointing at the right clause.
  • Policy: invalid-input tests cover each documented forbidden combination; contract doc committed with the module.
  • Cost: apply-time tests bounded (timeout, auto-destroy verified, tagged resources for leak sweeps); CI split keeps per-PR cost at plan-time-only.

Pitfalls

  • Tests that encode bugs. Generating tests from implementation instead of contract freezes current behavior, defects included. Contract first, always.
  • Vacuous assertions.condition = true-shaped checks and assertions on attributes the variable never affects. Mutation-checking is the antidote.
  • LocalStack false confidence. IAM is permissive, some services stubbed. Anything security-relevant must also run against a real account before release.
  • Orphaned test infrastructure. Failed apply-time runs leak resources. Auto-destroy in teardown plus a scheduled tag-based sweep of the sandbox account.

Restricted-environment variant

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.

Success criteria

  • Contract document exists, reviewed, with all QUESTIONs resolved.
  • Plan-time suite covers every contract clause and runs on each module PR in under two minutes.
  • 100% of tests passed the mutation check at least once, recorded in the PR.
  • A subsequent AI-drafted module change was accepted or rejected by the suite — the tests did the reviewing.
This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in