DevOps Peach

Advanced Terraform · Testing and CI/CD · Lesson 2 of 4

Testing with terraform test

Test files (*.tftest.hcl) run plan or apply with chosen variables and assert on the result; expect_failures tests validation, and mock providers make fast unit tests without real infrastructure.

Practice interactively →

terraform test (Terraform 1.6+) runs your module with test inputs and checks the result with assertions. Tests live in *.tftest.hcl files, usually in a tests/ folder next to the module.

flowchart LR
  TF["tests/app.tftest.hcl"] --> R1["run plan_defaults (command = plan)"]
  R1 --> R2["run rejects_bad_env (expect_failures)"]
  R2 --> R3["run apply_real (command = apply)"]
  R3 --> D["Destroy everything created"]
  MK["mock_provider docker"] -.-> R1
  %% step TF: A test file holds one or more run blocks that execute in order.
  %% step R1: A plan-only run is fast and creates nothing; assertions check the planned values.
  %% step R2: expect_failures passes only if the listed validation or precondition fails.
  %% step R3: An apply run creates real resources, so you can assert on computed values.
  %% step D: When the file finishes, Terraform destroys what the runs created, in reverse order.
  %% step MK: A mock provider returns fake values, so even apply runs need no real infrastructure.

A test file

# modules/app/tests/app.tftest.hcl
variables {
  name        = "shop-api"
  environment = "dev"
}

run "names_follow_convention" {
  command = plan
  assert {
    condition     = docker_container.this.name == "shop-api-dev"
    error_message = "container name must be <name>-<environment>"
  }
}

run "rejects_unknown_environment" {
  command = plan
  variables {
    environment = "qa"
  }
  expect_failures = [var.environment]
}

run "prod_needs_two_replicas" {
  command = plan
  variables {
    environment = "prod"
    replicas    = 1
  }
  expect_failures = [docker_container.this]     # its precondition must fail
}
cd modules/app
terraform init
terraform test                         # all files in . and tests/
terraform test -filter=tests/app.tftest.hcl -verbose

Unit tests with mocks

mock_provider "docker" {
  mock_resource "docker_container" {
    defaults = { id = "mock-1234", exit_code = null }
  }
}

With a mock provider, command = apply returns made-up computed values instead of calling Docker or a cloud API: fast and free. Use override_resource or override_data inside a run to fix one value.

What to test

Test type How Speed
Input validation command = plan + expect_failures Seconds
Naming, counts, conditionals command = plan + assert Seconds
Logic with computed values Mock provider + command = apply Seconds
Real integration command = apply against LocalStack or a sandbox account Minutes, costs money in a real cloud

Each run can also use a different module with module { source = "./tests/setup" } to create prerequisites first.

Traps: an apply run that fails half-way can leave resources behind if the destroy also fails, so run integration tests in a throwaway account or LocalStack; and assertions on values that are (known after apply) fail in a command = plan run, so switch that run to apply (or mock it).

Practice interactively →