Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 10 min read

Ordering run Blocks in Terraform Native Tests for Fast, Reliable Suites

Terraform native tests run top to bottom, share state, and mix free plan checks with billable applies. Here's how to order run blocks so suites stay fast, cheap, and trustworthy.

  • #terraform
  • #ai
  • #testing
  • #tftest
  • #ci

Every Terraform native test tutorial shows the same thing: one run block, a couple of asserts, done. That’s enough to feel productive and not enough to ship a real suite. The moment you have more than one run block, the framework’s actual behavior starts to matter — and it’s the part the single-run examples never mention.

run blocks execute top to bottom, they share and mutate state as they go, and each one chooses between a free, instant plan and a slow, billable apply. How you order them decides whether your suite is a fast gate or a flaky tax on every PR.

The three facts that drive everything

  1. Order is file order. run blocks run sequentially, top to bottom.
  2. State carries forward. Each run can build on the state the previous run left behind.
  3. plan is free; apply is real. command = plan evaluates without creating resources. command = apply creates real, billable infrastructure against real cloud APIs.

Internalize those three and good ordering falls out almost automatically.

Rule one: plan-only assertions go first

A surprising amount of what you want to test needs no apply at all. Variable validation, conditional resource counts, computed expressions, and expected-error cases can all be proven from a plan:

# tests/defaults.tftest.hcl

run "rejects_invalid_cidr" {
  command = plan

  variables {
    vpc_cidr = "not-a-cidr"
  }

  expect_failures = [var.vpc_cidr]
}

run "applies_default_tags" {
  command = plan

  assert {
    condition     = aws_vpc.main.tags["managed_by"] == "terraform"
    error_message = "Default managed_by tag was not applied."
  }
}

These cost nothing and run in seconds. Front-loading them means a broken module fails fast — before you’ve spun up a single resource. There’s no reason to wait on a multi-minute apply to discover a variable validation is wrong.

Rule two: order the apply runs deliberately

For the assertions that genuinely need real infrastructure, sequence them so each builds on the last and the likeliest failures surface earliest:

run "create_network" {
  command = apply
}

run "create_app_on_network" {
  command = apply

  variables {
    subnet_id = run.create_network.subnet_id
  }

  assert {
    condition     = aws_instance.app.subnet_id == run.create_network.subnet_id
    error_message = "App did not land in the expected subnet."
  }
}

Later runs reference earlier runs’ outputs via run.<name>.<output>, so state flows forward cleanly. Putting the most failure-prone apply early means you don’t pay for the whole chain before finding the break.

Rule three: separate setup from assertions

Use a run block with a module {} source to stand up shared fixtures, and keep that distinct from the runs that assert behavior:

run "setup" {
  module {
    source = "./tests/fixtures/network"
  }
}

When a setup run fails, you immediately know it’s a fixture problem, not a logic bug in the module under test. Mixing the two makes failures ambiguous.

Cleanup — and what happens when a run fails

At the end of a test, terraform test destroys what it created, in reverse order. That’s the happy path. If a run fails partway through, resources created before the failure can leak before the automatic destroy runs. So after any early failure, check your sandbox for orphans.

The cleanest defense is to prefer command = plan wherever an apply isn’t strictly required. A plan-only run creates nothing, so it can’t leak anything. Reserve applies for the assertions that truly need live resources.

Never point applies at production

command = apply runs create real, billable resources. Run them in a disposable sandbox account, never against production state. This isn’t a style preference — a misconfigured test apply against prod is an incident. Our native test framework prompt and our run block ordering prompt both bake the sandbox requirement in.

Let AI draft the ordering, then review the apply list

Sequencing a suite — deciding what can be plan-only, what must apply, and in what order — is a great task to hand to an AI assistant, as long as you review exactly which real resources it will create. A prompt like:

Here’s my module and the behaviors I want to test: variable validation, a conditional resource, and that the app lands in the right subnet. Order the run blocks: plan-only first, then any applies. For each, label plan vs apply and justify it. Flag which runs create billable resources so I can confirm before wiring it into CI.

A good answer returns an ordered suite with the validation and conditional-resource checks as plan, only the subnet placement as apply, plus:

The create_network and create_app runs create a VPC, subnet, and an instance — real, billable. Run these in a sandbox account; gate them nightly if cost matters, and keep the plan-only checks on every PR.

That apply inventory is what you verify before trusting the suite. The AI drafts a mostly-plan-based, well-ordered suite; you confirm the real-resource footprint in a sandbox first.

The ordering checklist

  • Plan-only first — validation, conditionals, computed values, expect_failures.
  • Order applies by dependency and risk — build forward, fail early.
  • Separate fixtures from assertions with module {} setup runs.
  • Prefer plan over apply to avoid creating (and leaking) real resources.
  • Sandbox only for apply runs — never production state.

Ordered well, a native test suite is fast enough to run on every PR and trustworthy enough to gate merges. Ordered badly, it’s a slow, flaky, occasionally expensive thing people learn to skip. For more on testing infrastructure code, browse our Terraform testing guides.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.