Terraform / Sensitive data, governance, and recovery / Lesson 14
Validation, tests, checks, and their execution effects
Choose the appropriate assertion boundary and remember that a Terraform test can perform real planning or application.
On this page
Lesson goals
- Compare variable validation, preconditions, postconditions, checks, and test assertions.
- Distinguish formatting, static validation, mocked tests, plan tests, and integration tests.
Different assertions stop or report at different boundaries
Variable validation checks an input domain. Preconditions and postconditions express supported resource, data, or output contracts at their documented evaluation points. A check block supports continuing validation and reports failed assertions as warnings rather than acting as a universal enforcement gate. Terraform test assertions evaluate expected behavior within test runs. Choose the mechanism from the requirement and failure semantics instead of placing every rule in a check and assuming unsafe changes are blocked. Unknown values can also defer when a condition becomes evaluable.
A pure output contract is easier to reason about
The complete root module below normalizes a service name and attaches a precondition to its output. It contains no managed resources or external data reads, but remains non-runnable editorial HCL because Terraform is not available for validation here. The contract requires the normalized name to start with atlas-. A successful text inspection is not an observed Terraform assertion result. Keep runtime and provider-dependent testing separate from this fixed expression-level example.
terraform {
required_version = ">= 1.16.0, < 1.17.0"
}
variable "service_name" {
type = string
default = "atlas-api"
}
locals {
normalized = lower(var.service_name)
}
output "name" {
value = local.normalized
precondition {
condition = startswith(local.normalized, "atlas-")
error_message = "Normalized service names must start with atlas-."
}
}
The declared default normalizes to atlas-api and satisfies the prefix rule logically. The precondition is an enforcement contract at its supported output boundary, not a warning-only check block. No Terraform command or assertion is executed.
A test file must declare its operation deliberately
Terraform test run blocks default to apply unless configured otherwise. That can create or change infrastructure and requires cleanup and independent authorization. The companion test below explicitly chooses plan and checks a known output from the preceding fixture. Even a plan test can involve providers, backends, or data reads in a different configuration, so the word test does not make an unknown project harmless. This test file is shown only; no test runner, provider installation, or application is performed.
run "normalized_name" {
command = plan
variables {
service_name = "ATLAS-API"
}
assert {
condition = output.name == "atlas-api"
error_message = "The output must publish the normalized catalogue name."
}
}
Place this file with the preceding root as tests/name.tftest.hcl. The assertion compares the public output with the declared contract rather than a provider-created id. This is a static teaching example, not a claim of a passing Terraform test.
Mocking changes evidence, not production truth
Supported mock providers and overrides can isolate module behavior from real remote systems, but their schemas and generated values do not certify real API permissions, quotas, readiness, or failure recovery. Plan-only tests, mocked tests, and provider integration tests therefore answer different questions. Define which properties each test can establish and which remain untested. A formatting check can detect style differences; static validation checks configuration consistency under its prerequisites; neither is a substitute for reviewing an actual authorized change plan.
Keep failure cases and execution boundaries explicit
Test stable identities, input boundaries, missing and unknown values, output compatibility, replacement intent, and policy rejection where those belong to the module contract. Avoid tests that merely repeat implementation text without expressing meaningful behavior. Preserve version and dependency evidence with results, and distinguish expected assertion failures from unrelated type or provider errors. Tests should be safe to run only within their approved environment and cleanup model. This course does not invoke validate, test, plan, apply, or any cloud-facing operation.
Common errors and fixes
A warning-only check is expected to block application.
- Why it happens
- Continuing validation was confused with an enforcement precondition or policy gate.
- How to fix it
- Choose the documented blocking mechanism appropriate to the requirement.
- Prevent it next time
- Review assertion severity and evaluation timing explicitly.
A test unexpectedly creates resources.
- Why it happens
- The default apply operation or provider-dependent behavior was not reviewed.
- How to fix it
- Stop through the authorized process and reconcile created state and objects.
- Prevent it next time
- Declare test operation, scope, credentials, and cleanup before execution.
A mocked success is reported as verified production readiness.
- Why it happens
- Evidence from an isolated contract test was generalized beyond its boundary.
- How to fix it
- State the tested properties and require separate real-environment acceptance evidence.
- Prevent it next time
- Label formatting, static, mocked, plan, and integration results separately.
Check your understanding
1. Why does the companion test explicitly say command=plan?
Show worked solution
A test run otherwise defaults to application. The explicit operation communicates the intended verification scope for this fixture. It still does not authorize running arbitrary plan tests, because another configuration can read providers or backends. Review the actual source and execution context before any separately approved test.
2. Can a check warning replace a hard requirement to reject an unsafe input?
Show worked solution
No. Use the appropriate input validation, precondition, or policy enforcement boundary according to the contract. A warning can be useful observability, but treating it as rejection changes the safety model. Document expected failure behavior and test the enforcement path rather than relying on a reassuring message.
Key takeaways
- Tests can perform real operations; declare their scope and command.
- Report exactly what formatting, validation, mocking, or integration evidence proves.