TrademarkTrademark
Features
Documentation
  1. Learning Center
  2. CI/CD and GitOps for Terraform & OpenTofu

Article · part of a guide

tofu test and terraform test: Testing OpenTofu and Terraform Modules

terraform test, tofu test, and Terratest solve different layers of the testing problem. Here's how each one works and when to reach for it.

Key takeaways

  1. terraform test has been Terraform's built-in, generally available testing framework since v1.6 (October 2023). Tests live in .tftest.hcl files made of run blocks, and mock_provider, added in v1.7, lets a run assert against fake resources instead of provisioning real infrastructure.
  2. OpenTofu's tofu test moved out of experimental status in OpenTofu 1.6.0, released January 10, 2024. It uses the same run/assert syntax, but also recognizes .tofutest.hcl and .tofutest.json files, which take precedence over .tftest.hcl/.tftest.json of the same name so module authors can keep separate Terraform- and OpenTofu-specific test suites side by side.
  3. Neither terraform test nor tofu test replaces integration testing against real infrastructure. That's still Terratest's job, and Terratest remains actively maintained: v2 is in development, and the v1 line is scheduled to keep receiving security fixes until at least 12 months after v2.0.0 reaches general availability.
  4. Kitchen-Terraform, the older Ruby/InSpec-based option, was deprecated the same week terraform test shipped and its GitHub repository was archived on October 22, 2024. It isn't a reasonable choice for a new project.
  5. Scalr's private module registry can run tofu test against private modules directly, with provider configurations injectable as test credentials and test runs triggerable automatically on every pull request or module sync.

Most Terraform and OpenTofu pipelines run validate, fmt, and a linter, then call it "tested." That catches syntax errors and style violations, not logic errors. A module that provisions the wrong subnet, computes a count wrong, or produces an output nobody actually asked for will pass every one of those checks and still be broken.

Both engines now ship native commands built specifically for this: terraform test and tofu test. Neither is new anymore, but the details of how they work, where they diverge between the two engines, and where they stop being enough are still widely misunderstood. This post covers both commands, plus Terratest and Kitchen-Terraform, the two tools people reach for when native testing isn't the right layer.

How does terraform test work?

terraform test became generally available in Terraform 1.6, announced October 4, 2023. Tests live in files with a .tftest.hcl extension, most commonly inside a tests/ directory at the root of a module.

# tests/main.tftest.hcl
 
variables {
  environment = "staging"
}
 
run "creates_expected_instance_count" {
  command = plan
 
  assert {
    condition     = length(aws_instance.app) == 2
    error_message = "Expected 2 app instances, got a different count"
  }
}
 
run "instance_has_required_tags" {
  command = apply
 
  assert {
    condition     = aws_instance.app[0].tags["Environment"] == "staging"
    error_message = "Environment tag was not set correctly"
  }
}

Each run block behaves like its own Terraform operation against the configuration under test. A run with command = plan only builds a plan and asserts against it, so it never touches a real provider API and executes fast enough to run on every commit. A run with command = apply actually provisions resources, asserts against the resulting state, and then tears everything down when the test file finishes, which makes it slower and, unless you mock the provider, dependent on real cloud credentials.

That second part changed in Terraform 1.7, which added the mock_provider block. A mock_provider stands in for a real provider and returns synthetic values instead of calling out to an actual API, so an apply-command run can exercise resource logic without creating anything real. Declared without an alias, as below, it replaces the aws provider for every run in the file:

mock_provider "aws" {}
 
run "mocked_apply_still_checks_logic" {
  command = apply
 
  assert {
    condition     = aws_instance.app[0].instance_type == "t3.medium"
    error_message = "Wrong instance type"
  }
}

For CI/CD, the flag worth knowing about is -junit-xml, which writes test results in JUnit XML format so any CI system that already parses JUnit output (which is most of them) can render Terraform test results natively. It went through an experimental phase, based on public feedback about how to map terraform test concepts onto the JUnit XML schema, before reaching general availability in Terraform 1.11.

How is tofu test different from terraform test?

OpenTofu forked from Terraform 1.5.7, before the 1.6 test framework existed, so it didn't inherit terraform test for free. OpenTofu built its own version. tofu test moved out of experimental status in OpenTofu 1.6.0, released January 10, 2024, which was also OpenTofu's own general-availability release.

The core syntax is deliberately compatible: run blocks, assert blocks, mock_provider. A .tftest.hcl file written for terraform test will generally also run under tofu test. Where OpenTofu goes further is file extensions. In addition to .tftest.hcl and .tftest.json, tofu test also recognizes .tofutest.hcl and .tofutest.json. If a directory has both main.tftest.hcl and main.tofutest.hcl, OpenTofu runs the .tofutest.hcl version and ignores the other.

That precedence rule is more useful than it looks. It gives module authors a clean way to maintain one shared test suite that runs identically under both engines, plus a second, OpenTofu-specific suite that only kicks in for tofu test, without needing separate directories or conditional logic. It matters most for modules that lean on capabilities where the two engines have diverged, such as OpenTofu's state encryption or its own ephemeral-resource implementation, which arrived later than Terraform's. For the full version history of ephemeral resources and write-only arguments across both engines, see secrets in Terraform state.

tofu test also supports the same override_resource, override_data, and override_module blocks for stubbing out specific parts of a configuration, and the same practical CLI options: -test-directory to point at a non-default test location, -filter to run a subset of test files, -var and -var-file for input variables, and -json / -json-into=FILENAME for machine-readable output (the latter added in OpenTofu 1.12, letting you write structured results to a file while still printing human-readable output to the terminal).

How do you write your first tofu test?

Put a test file in a tests/ directory at the root of the module, run tofu init, then run tofu test. That's the whole loop. By default tofu test looks for test files in the current directory and in tests/, runs each run block in order, and reports a pass or fail for each one.

A good first test is a plan-mode run against a mocked provider. It checks that an input flows through to the resource it's supposed to configure, and it doesn't need cloud credentials or create anything:

# tests/defaults.tftest.hcl
 
mock_provider "aws" {}
 
variables {
  bucket_name = "scalr-test-bucket"
}
 
run "bucket_uses_input_name" {
  command = plan
 
  assert {
    condition     = aws_s3_bucket.this.bucket == "scalr-test-bucket"
    error_message = "Bucket name doesn't match the bucket_name input"
  }
}
tofu init
tofu test
tofu test -filter=tests/defaults.tftest.hcl
tofu test -verbose

-filter narrows the run to specific test files, given as paths relative to the working directory. -verbose prints the plan or state for each run block, which is the first thing to reach for when an assertion fails and its error message doesn't explain why.

Because the file uses the .tftest.hcl extension, the same test also runs under terraform test. Once the module starts depending on something only OpenTofu has, copy the file to defaults.tofutest.hcl and change the copy. tofu test will run that version, and Terraform won't read it at all.

Should you use tofu test, Terratest, or both?

Tool Language Touches real infra? Best for Status
terraform test HCL Optional (mock_provider available since 1.7) Fast, in-repo unit tests of module logic Active, GA since Terraform 1.6
tofu test HCL Optional (mock_provider supported) Same as above, for OpenTofu, plus .tofutest.hcl-based dual test suites Active, GA since OpenTofu 1.6.0
Terratest Go Yes, by design Integration and end-to-end testing of real infrastructure Actively maintained; v2 in development, v1 in security-fix maintenance
Kitchen-Terraform Ruby (InSpec) Yes Legacy integration testing Deprecated Oct 2023, GitHub repo archived Oct 22, 2024

The native frameworks and Terratest aren't competing for the same job, so most teams that test seriously use both rather than picking one. terraform test and tofu test are fast enough to run on every pull request and check whether a module's logic does what you expect: the right count of resources, the right tags, the right computed outputs. Terratest is slower and provisions real infrastructure, but it's the only one of the three that can tell you whether the actual cloud resources it created behave the way you expect, which matters for things like verifying that a security group blocks the traffic it's supposed to, or that a load balancer health check passes.

Kitchen-Terraform doesn't belong in that comparison anymore except as a migration note. It was deprecated the same week Terraform's native framework was announced, and its maintainers were explicit that users should move to terraform test. The repository was archived on GitHub on October 22, 2024. If you inherited a codebase still using it, that's a signal to migrate, not a tool to keep building on.

Where do tests run in a CI/CD pipeline?

This post is about the tools themselves. For how these fit into a full CI/CD pipeline, including where static validation and drift detection sit relative to unit and integration tests, see the testing section of our CI/CD and GitOps guide. Typically, static checks (fmt, validate, linting) run on every commit, terraform test or tofu test runs on every pull request since it's fast, and Terratest-style integration tests run less often, typically on merges to a main branch or before a release, because real infrastructure costs time and money to spin up and tear down.

How does Scalr run tofu test on modules?

Scalr's private module registry has a Testing tab on every module that runs tofu test against the module's own test suite before that module gets used in a workspace, which catches broken modules before they reach a real environment rather than after. The module registry, testing included, is part of Scalr's core feature set on both the Business and Enterprise plans, not an add-on you have to unlock separately. Since module tests can require real credentials, a test suite can be linked to one or more of Scalr's provider configurations, and those credentials get injected into the test run as environment variables automatically, so you're not managing separate secrets for testing versus production runs.

Testing doesn't have to be a manual step you remember to click. Scalr can trigger a module test run automatically whenever a pull request against the module's repository is opened or updated, and separately, whenever a new module version syncs in from VCS. Both are configured per module, so you can be as strict or as lightweight as a given module warrants.

Workspace-level checks that don't belong in a module's test suite, such as tflint or a Checkov scan, can run as Scalr custom hooks before or after a plan or apply.

terraform test and tofu test check your module's logic without needing real infrastructure, while Terratest is still the right tool for checking whether real infrastructure behaves correctly. Kitchen-Terraform isn't a live option anymore. If you publish modules through a registry, running tofu test automatically on every change, with real credentials injected safely, closes the gap between "someone wrote a test" and "the test ran before this module shipped."

Frequently asked questions

What is terraform test?

terraform test is Terraform's built-in testing command, generally available since Terraform 1.6 (announced October 4, 2023). Tests are written in .tftest.hcl files as a series of run blocks, each of which executes a plan or apply against your configuration and checks the result with assert blocks.

Does OpenTofu have its own version of terraform test?

Yes. tofu test moved out of experimental status in OpenTofu 1.6.0, released January 10, 2024, alongside OpenTofu's own general-availability launch. The syntax is compatible with Terraform's run/assert model, and OpenTofu additionally supports .tofutest.hcl and .tofutest.json file extensions.

How do you run tofu test?

Put a .tftest.hcl or .tofutest.hcl file in a tests/ directory at the root of the module, run tofu init, then run tofu test. tofu test finds test files in the current directory and in tests/ by default. Use -filter with a file path to run one test file, and -verbose to print the plan or state for each run block.

What's the difference between a .tftest.hcl file and a .tofutest.hcl file?

Both are valid test file extensions in OpenTofu, but if a directory contains both a .tofutest.hcl file and a .tftest.hcl file with the same base name, OpenTofu runs the .tofutest.hcl version. This lets a module author write one test suite that runs under plain Terraform and a separate, OpenTofu-specific suite that only tofu test picks up, useful once a module's behavior starts to diverge between the two engines.

Do terraform test and tofu test create real cloud resources?

Only if you let them. A run block with command = plan never touches a real provider API. A run block with command = apply will create real resources unless the provider in that run is replaced with mock_provider (added in Terraform 1.7 and supported by tofu test as well), which returns synthetic data instead of calling out to a cloud API.

Is Terratest still a good choice for testing Terraform or OpenTofu modules?

Yes, for integration testing specifically. Terratest is a Go library that spins up real infrastructure, runs assertions against it, and tears it down, which is a different job from the plan-time unit testing that terraform test and tofu test do. It remains actively maintained, with a v2 in development and the v1 line continuing to receive security fixes for at least 12 months after v2.0.0 reaches general availability.

Is Kitchen-Terraform still maintained?

No. Kitchen-Terraform was deprecated the week Terraform announced its native test framework in Terraform 1.6 (October 2023) and its maintainers recommended migrating to terraform test. The GitHub repository was archived on October 22, 2024, and it should not be used for new projects.

Can Scalr run terraform test or tofu test automatically?

Yes, at the module level. Scalr's private module registry has a Testing tab per module that runs tofu test against the module's own test suite. Provider configurations can be linked to a module's test suite so credentials are injected as environment variables during the run, and Scalr can trigger a test run automatically whenever a pull request is opened or updated, or whenever a new module version syncs from VCS.

About the author

Ryan Fee

director of platform engineering at Scalr

Ryan Fee is the director of platform engineering at Scalr, with over 15 years of experience improving infrastructure experiences at companies large and small.

Part of this guide

25 sheets

CI/CD and GitOps for Terraform & OpenTofu

24 articles