Learn OpenTofu - Formatting, Linting & Native Testing Framework (tofu test)
Episode 11 of 21

Learn OpenTofu - Formatting, Linting & Native Testing Framework (tofu test)

Before applying, code must be tidy and tested. This episode covers automatic tidying with tofu fmt and tofu validate, static analysis via tflint, Checkov, and Trivy, and the native tofu test testing framework with .tftest.hcl files without Go or external Terratest.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In the previous episode 10 we covered reusable modules — how to package a VPC, database, or a complete app into "LEGO blocks" callable again from the OpenTofu Registry, Git repositories, or a local path. Modules make code far DRYer, but one question still hangs: how do we know the code is correct, tidy, and safe before hitting tofu apply? In the real world, infrastructure broken by a single mis-typed HCL line is far more common than people imagine — and all of it can be prevented by a quality layer that feels boring: formatting, linting, and testing.

In episode 11 we'll build three sensible safety layers to install in a production IaC repository. First, automatically tidying and validating code with tofu fmt and tofu validate. Second, static security analysis with tflint, Checkov, and Trivy. Third — the main guest — OpenTofu's native testing framework: tofu test with .tftest.hcl files that let you write assertions without learning Go or assembling Terratest. Let's start.

Main Discussion

Ending Cosmetic Debates with tofu fmt

Every engineer has spent half an hour debating indentation in code review. In ordinary app development that debate is a matter of taste; in IaC, differing styles between files make diffs noisy, harden review, and hide genuinely important changes. tofu fmt eliminates this problem forever: it rewrites .tf files to conform to the official canon — two-space indentation, aligned = signs, and a blank line after each block.

tofu fmt -recursive descends into subfolders, so the entire module structure gets tidied in one command. For CI pipelines, a check mode makes it a gate that fails with a non-zero exit code when any file isn't formatted:

fmt & validate in the terminal and CI
# Tidy all .tf files including in module subfolders
tofu fmt -recursive
 
# Check mode for CI: fails if any file isn't formatted
tofu fmt -check -recursive
 
# Show the diff without rewriting files
tofu fmt -diff -recursive
 
# Validate syntax & references (required after tofu init)
tofu init
tofu validate

tofu validate checks HCL syntax, expression validity, cross-resource references, and provider configuration consistency. It never touches the cloud, so it runs very fast — that's why it's the first gate in the pipeline. One important note: tofu validate needs the provider schema, so always run tofu init first.

Tip

Install tofu fmt -check -recursive as a pre-commit hook or the first pipeline step. These two commands catch 80 percent of problems before the code is ever seen by humans — cheap, fast, and automatic.

Static Security Analysis: tflint, Checkov, and Trivy

fmt and validate only answer "is the code syntactically correct" — not "is the code safe." For that we need three complementary static scanners:

tflint — a linter that understands the provider schema deeply. It catches deprecated arguments, misspelled resource names, or configurations not supported by the provider version in use. Run tflint --init once to download the provider plugins.

Checkov — a misconfiguration scanner from Palo Alto Networks. It reads semantics, not just syntax: a public S3 bucket, a security group with port 22 open to 0.0.0.0/0, or an unencrypted disk are reported immediately, and it exits with code 1 when findings exist.

Trivytrivy config . scans IaC files against a CVE database and best practices, and also detects secrets leaked in code. It can also scan container images — one tool for two domains.

Three layers of static analysis
# tflint — provider-specific linter
tflint --init
tflint
 
# Checkov — misconfiguration scan, exit 1 if findings exist
checkov -d . --compact
 
# Trivy — IaC config scan, only severe levels
trivy config --severity HIGH,CRITICAL .

Note

Static scanners read code, not state. Something that passes Checkov and Trivy isn't necessarily safe in the real world — a human plan review and approval gates remain mandatory in production.

Native Testing Framework: tofu test

Before OpenTofu, testing IaC meant adopting Terratest: writing Go code, compiling binaries, managing go.mod and dependencies — a heavy burden for a small assertion like "the VPC must use the correct CIDR." OpenTofu flips this paradigm with a native HCL-based testing framework.

Test files named *.tftest.hcl sit next to the module. A test consists of a run block with command = plan or apply, and one or more assert blocks containing a condition plus error_message. OpenTofu creates an isolated state for each test and cleans it up automatically when finished:

main.tftest.hcl
run "verify_vpc" {
  command = apply
 
  assert {
    condition     = aws_vpc.main.cidr_block == "10.0.0.0/16"
    error_message = "The VPC must use CIDR 10.0.0.0/16"
  }
 
  assert {
    condition     = aws_vpc.main.enable_dns_hostnames == true
    error_message = "DNS hostnames must be enabled for an EKS cluster"
  }
}

Running a test is as simple as:

Running tofu test
# Run all tests in the module directory
tofu test
 
# Only a specific test, with detailed output
tofu test -filter=verify_vpc -verbose

For testing without touching a real cloud, OpenTofu provides mock_provider and override_resource: resources can be mocked so assertions are verified purely in plan mode. This feature is what removes the need for Terratest in most cases — IaC testing can finally be written by the same people who write the HCL, without switching languages.

LayerTypeWhat it catchesCI-ready
tofu fmtFormattingInconsistent code styleYes, -check mode
tofu validateValidationSyntax errors, references, providersYes
tflintLintingDeprecated arguments, schema typosYes
CheckovSecurity scanOpen ports, public buckets, plain disksYes
TrivySecurity scanMisconfig, CVEs, leaked secretsYes
tofu testTestingInfrastructure behavior via assertionsYes

Warning

Run tofu test on every PR, but restrict command = apply to non-production environments or local providers like LocalStack. Tests with apply actually create resources in the cloud — letting them hit the wrong environment is the most expensive mistake possible in CI.

Conclusion

In episode 11 we installed three layers of quality protection:

  • tofu fmt -recursive tidies all code to a single canon, and -check mode makes it a CI gate.
  • tofu validate validates syntax and references before touching the cloud.
  • tflint, Checkov, and Trivy catch linting issues, misconfigurations, CVEs, and leaked secrets.
  • tofu test with .tftest.hcl files gives native assertions without Go or Terratest, plus mock_provider for pure plan mode.

Tidy, tested code is only the foundation — the next problem is how to manage hundreds of infrastructure folders without repeating the same configuration over and over. That's Terragrunt's job. In the next episode, episode 12, we'll integrate OpenTofu with Terragrunt: running the tofu binary via terragrunt --tofu, and arranging a DRY architecture for multi-environment Dev, Staging, and Prod with centralized backends and providers. See you there!

Learn OpenTofu - Formatting, Linting & Native Testing Framework (tofu test) | Learn OpenTofu