TrademarkTrademark
Features
Documentation

Terraform Testing: terraform test, tofu test, Terratest

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.
Ryan FeeJuly 9, 2026
Key takeaways
  • 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.
  • 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.
  • 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.
  • 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.
  • 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 in depth, plus Terratest and Kitchen-Terraform, the two tools people reach for when native testing isn't the right layer.

terraform test: Terraform's native framework

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:

mock_provider "aws" {}
 
run "mocked_apply_still_checks_logic" {
  command = apply
 
  providers = {
    aws = aws.mock_provider
  }
 
  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.

tofu test: OpenTofu's equivalent, with its own extensions

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).

terraform test / tofu test vs. Terratest vs. Kitchen-Terraform

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 a security group truly blocks the traffic it's supposed to, or that a load balancer health check actually 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 this fits in a broader testing strategy

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. The short version: 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.

Where Scalr fits

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.

Module-level testing covers the module itself, but workspace-level runs often need their own validation, like a policy check or a custom lint step, that isn't naturally part of a tofu test suite. Scalr's custom hooks handle that case: you can attach a script to run before or after a plan or apply, and that script has access to the plan JSON and to a SCALR_TERRAFORM_OPERATION variable telling it which stage it's running in. That's a reasonable place to run tflint, a Checkov scan, or a custom validation script as part of the same run, alongside the module-level tofu test coverage.

The short version: terraform test and tofu test check your module's logic without needing real infrastructure. Terratest checks whether real infrastructure actually behaves correctly, and it's still the right tool for that job. Kitchen-Terraform isn't a live option anymore. And if you're publishing 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 actually 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.

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 Feedirector 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.