Subscribe to the Spacelift Blog newsletter, Mission Infrastructure |

Sign Up ➡️

Terraform

How to Test Terraform Code – Strategies & Tools

How to test your Terraform code

Terraform testing is the process of automatically verifying that your Terraform configurations are syntactically correct, functionally accurate, and compliant with security and operational policies. It spans multiple layers: static analysis (linting, validation), unit and integration tests using frameworks like Terratest or Terraform’s native terraform test command, and drift detection to catch real-world changes. 

A solid testing strategy reduces deployment risk and makes infrastructure changes reproducible and auditable.

In this article, we will take a look at Terraform testing strategies using different types of commonly used testing, tools and platforms. Let’s jump straight in!

We will cover:

  1. What is the Terraform test file syntax?
  2. What is the Terraform test command?
  3. Terraform testing strategies
  4. When to use each Terraform testing strategy?

What is the Terraform test file syntax?

Terraform discovers test files inside your configuration by checking the following extensions:

  • .tftest.hcl
  • .tftest.json

Every test file is composed of root-level attributes and blocks as follows:

  • One or more run blocks
  • One or zero variables block
  • Zero to many provider blocks.

Terraform runs blocks sequentially, mimicking a sequence of Terraform commands in the configuration directory. The sequence in which the variables and provider blocks are arranged doesn’t matter, as Terraform evaluates all values within these blocks at the start of the testing process.

For clarity, it is advisable to place your variables and provider blocks at the start of the test file.

Terraform test file example

The run block command can be either plan or apply, if you omit to add it, the default value is a plan.

run "example_test" {
 command = plan

 // Define any variables required for your test
 variables {
   example_variable = "value"
 }

 // Assertions to validate the test outcome
 assert {
   condition     = example_resource.attribute == var.example_variable
   error_message = "Test failed: the attribute value was not as expected."
 }

 // Optionally, you can specify providers if your test requires it
 provider "aws" {
   region = "us-west-2"
 }
}

Inside the run block, you can include optional variables and assertions, and you can use the variables in assertions as you would any Terraform variable by prefixing them with var. Variables can also be defined globally (outside the run block), or just inside the run block for them to be accessed only by that particular run block.

Assertions in run blocks check a condition, and if that condition fails, it will be presented at the end of the Terraform verification.

You can define how many providers you want and specify them as part of the run block or outside of it.

What is the Terraform test command?

The terraform test command is used to run all the test files defined in your configuration.

What does the Terraform test command do?

The terraform test command reads the Terraform test files defined in your configuration files by looking at all the files that have the .tftest.json and .tftest.hcl extensions and executes the tests defined in those files. By default, Terraform will search for these files in the current directory and also in the specified testing directory (by default, it is tests).

Terraform test command usage options

The terraform test command can receive a couple of optional attributes:

  • -test-directory=<directory> – overrides the directory that Terraform looks into for test files
  • -json – displays JSON output for your testing results
  • -verbose – prints out the plan or state for each run block within a test file
  • -filter=testfile – will limit the test operation on specific testfiles only

Terraform test command usage examples

  • terraform test -filter=tests/my_test.tftest.hcl – this will test only the tests/my_test.tftest.hcl
  • terraform test -verbose – will print out the plan or state for each run block
  • terraform test -test-directory=my_tests – will run the tests from the current directory and my_tests directory

Terraform testing strategies

As with any code, you should implement some testing strategies to help validate your code and ensure that your changes do not cause any unexpected issues or break existing functionality.

Common Terraform testing strategies include:

  1. Integration testing
  2. Unit testing
  3. End-to-end (E2E) testing
  4. Linting
  5. Compliance testing
  6. Drift testing

In this article, we will take a look at each and how to use them with Terraform code.

Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Integration testing

Integration testing involves testing your entire infrastructure configuration, including any dependencies on other resources, to ensure that they work together correctly. Terraform has built-in dependency mapping and management that can be utilized to make sure the changes being made are as expected.

You can use tools like Terratest or Kitchen-Terraform for integration testing.

The following tools can also be used in the workflow, and including them in the CI pipeline will form Integration Testing:

  • Terraform fmt — to format the code correctly.
  • Terraform validate — to verify the syntax.
  • Terraform plan — to verify the config file will work as expected.
  • TFLint — to verify the contents of the configuration as well as the syntax and structure, also checks account limits (e.g. that a VM instance type is valid, and that the limit on the number of VMs in Azure has not been reached).
  • Kitchen-Terraform Kitchen-Terraform is an open-source tool that provides a framework for writing automated tests that validate the configuration and behavior of Terraform code, including testing for errors, resource creation, destruction, and validation of outputs. Kitchen-Terraform uses a combination of Ruby, Terraform, and Test Kitchen to spin up infrastructure in a sandbox environment, run tests, and destroy the infrastructure once testing is complete.
  • Terratest — Terratest is an open-source testing framework for testing Terraform that can also be used to test Kubernetes, Docker, and Packer, amongst others. Terratest enables automated testing of infrastructure code in a sandbox environment to validate that it meets requirements, functions as expected, and is reliable. Tests are written in Go, and it provides a rich set of testing functions that allow the user to automate the entire testing process, including provisioning resources, running tests, and cleaning up resources. Terratest also provides built-in support for popular testing frameworks like Ginkgo and Gomega.

How to use Terratest

In the Terratest example below, a function is created to test the creation of an Azure storage account in a resource group, and a container in the storage account, then verifies that the storage account and container exist. The resources are then cleaned up after the test is complete.

package test

import (
 "context"
 "fmt"
 "testing"
 "time"

 "github.com/Azure/azure-sdk-for-go/storage"
 "github.com/gruntwork-io/terratest/modules/azure"
 "github.com/gruntwork-io/terratest/modules/random"
 "github.com/gruntwork-io/terratest/modules/testing"
)

func TestAzureStorageAccount(t *testing.T) {
 t.Parallel()

 // Set the Azure subscription ID and resource group where the Storage Account will be created
 uniqueID := random.UniqueId()
 subscriptionID := "YOUR_AZURE_SUBSCRIPTION_ID"
 resourceGroupName := fmt.Sprintf("test-rg-%s", uniqueID)

 // Create the resource group
 azure.CreateResourceGroup(t, resourceGroupName, "uksouth")

 // Set the Storage Account name and create the account
 accountName := fmt.Sprintf("teststorage%s", uniqueID)
 accountType := storage.StandardZRS
 location := "uksouth"
 account := azure.CreateStorageAccount(t, subscriptionID, resourceGroupName, accountName, accountType, location)

 // Create a container in the Storage Account
 ctx := context.Background()
 client, err := storage.NewBasicClient(accountName, azure.GenerateAccountKey(t, accountName))
 if err != nil {
  t.Fatalf("Failed to create Storage Account client: %v", err)
 }
 service := client.GetBlobService()
 containerName := fmt.Sprintf("testcontainer%s", uniqueID)
 if _, err := service.CreateContainer(ctx, containerName, nil); err != nil {
  t.Fatalf("Failed to create container: %v", err)
 }

 // Check if the container exists
 testing.WaitForTerraform(ctx, terraformOptions, nil)

 // Cleanup resources
 defer azure.DeleteResourceGroup(t, resourceGroupName)
}

Unit testing

Unit testing involves testing individual modules or resources in isolation to ensure that they work as expected. This can again be done using tools like Terratest or using Terraform’s built-in testing functionality.

Unit Testing with the Native Test Framework

Both Terraform (v1.6+) and OpenTofu support native unit testing using HCL-based .tftest.hcl files, no additional language or framework required. Unlike Terratest, which requires writing tests in Go, the native framework lets you write tests in the same declarative HCL you already use for your infrastructure code.

For unit tests, you use command = plan inside a run block so no real infrastructure is created. The example below tests that an EC2 instance variable validation works correctly:

# tests/instance_type.tftest.hcl

run "valid_instance_type" {
  command = plan

  variables {
    instance_type = "t2.micro"
  }

  assert {
    condition     = var.instance_type == "t2.micro"
    error_message = "Expected instance type t2.micro."
  }
}

run "invalid_instance_type" {
  command = plan

  variables {
    instance_type = "m5.24xlarge"
  }

  expect_failures = [var.instance_type]
}

OpenTofu also supports a .tofutest.hcl file extension for tests that should only run on OpenTofu — if both main.tftest.hcl and main.tofutest.hcl exist in the same directory, OpenTofu will load only the .tofutest.hcl file, which is useful for module authors who want to support both tools. 

Note on CDKTF: AWS CDK for Terraform (CDKTF) was sunset and archived by HashiCorp on December 10, 2025. The repository is now read-only with no ongoing maintenance. If you were using CDKTF with Jest or unittest for Terraform testing, the native .tftest.hcl framework is the recommended path forward.

End-to-End (E2E) testing

End-to-end testing involves testing your infrastructure configuration in a production-like environment to ensure that it works as expected in a real-world scenario. This can be done using tools like Terratest or manual testing in a staging environment.

Continuing with the Azure storage account example, let’s look at how we can use Terratest to implement end-to-end testing.

  1. Create a Terraform configuration file that defines an Azure Storage account and container:

main.tf

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "test" {
  name     = "test-resource-group"
  location = "uksouth"
}

resource "azurerm_storage_account" "test" {
  name                     = "teststorageaccount"
  resource_group_name      = azurerm_resource_group.test.name
  location                 = azurerm_resource_group.test.location
  account_tier             = "Standard"
  account_replication_type = "ZRS"

  tags = {
    environment = "test"
  }
}

resource "azurerm_storage_container" "test" {
  name                  = "testcontainer"
  storage_account_name  = azurerm_storage_account.test.name
  container_access_type = "private"
}
  1. Create a Terratest test file to deploy the Terraform configuration and validate the created Azure Storage account exists, along with testing that the container has the correct name and access type.

storage_test.go

package test

import (
 "context"
 "fmt"
 "os"
 "testing"

 "github.com/Azure/azure-sdk-for-go/profiles/latest/storage/mgmt/storage"
 "github.com/gruntwork-io/terratest/modules/azure"
 "github.com/gruntwork-io/terratest/modules/terraform"
 "github.com/stretchr/testify/assert"
)

func TestAzureStorageAccountAndContainer(t *testing.T) {
 t.Parallel()

 // Define the Terraform options with the desired variables
 terraformOptions := &terraform.Options{
  // The path to the Terraform code to be tested.
  TerraformDir: "../",

  // Variables to pass to our Terraform code using -var options
  Vars: map[string]interface{}{
   "environment": "test",
  },
 }

 // Deploy the Terraform code
 defer terraform.Destroy(t, terraformOptions)
 terraform.InitAndApply(t, terraformOptions)

 // Retrieve the Azure Storage account and container information using the Azure SDK
 storageAccountName := terraform.Output(t, terraformOptions, "storage_account_name")
 storageAccountGroupName := terraform.Output(t, terraformOptions, "resource_group_name")
 containerName := terraform.Output(t, terraformOptions, "container_name")

 // Authenticate using the environment variables or Azure CLI credentials
 azClient, err := azure.NewClientFromEnvironmentWithResource("https://storage.azure.com/")
 if err != nil {
  t.Fatal(err)
 }

 // Get the container properties
 container, err := azClient.GetBlobContainer(context.Background(), storageAccountGroupName, storageAccountName, containerName)
 if err != nil {
  t.Fatal(err)
 }

 // Check that the container was created with the expected configuration
 assert.Equal(t, "testcontainer", *container.Name)
 assert.Equal(t, "private", string(container.Properties.PublicAccess))

 fmt.Printf("Container %s was created successfully\n", containerName)
}
  1. Run the test by navigating to the directory where the go file is saved and run the following command.
go test -v

Contract testing

Contract testing checks if a configuration using a Terraform module gives properly formatted inputs. They verify the compatibility between the expected and actual inputs of a Terraform module, ensuring the module is used as intended by its author.

These tests utilize input validations, along with preconditions and postconditions, to check input combinations and errors, facilitating clear communication from the module creator to the users about the intended use of the module:

variable "instance_type" {
 type        = string
 description = "The instance type for the EC2 instance."

 validation {
   condition     = contains(["t2.micro", "t2.small", "t3.micro"], var.instance_type)
   error_message = "The instance type must be t2.micro, t2.small, or t3.micro."
 }
}

resource "aws_instance" "example" {
 instance_type = var.instance_type
}

In the above example, we ensure that an instance type can be only t2.micro, t2.small or t3.micro.

Mocks

Test mocking is a beta feature that lets you mock providers, resources and datasource for your tests, which lets you use terraform test without creating infrastructure or requiring credentials. They can be only used with terraform test and generate fake data for all the computed attributes.

Mock Providers

Mock Providers are used to create a dummy version of a provider, enabling the testing of Terraform configurations without actual cloud interactions. They can be used instead of a traditional provider block in your test, and they share the global namespace. Terraform won’t distinguish between a real and a mocked provider.

They will be defined with a “mock_provider” entry in your test files like so:

mock_provider "aws" {}

Note: Test mocking is available in Terraform v1.7.0 and later. This feature is in beta.

Overrides

Overrides can modify existing configurations for testing purposes, allowing for the alteration of resource attributes, data source attributes or even provider settings.

Each override block identifies the target—be it a resource, data source, or module—through a target attribute. For modules, an outputs attribute is used within override_module blocks to specify overrides. In contrast, override_resource and override_data blocks use a values attribute to define the overrides for resources and data sources, respectively.

Repeated blocks and nested attributes

Repeated blocks and nested attributes can also be tested with mocks to ensure that configurations with complex structures behave as expected, even when actual resources are not being manipulated.

For example, for every block inside of a resource that supports a dynamic block, you can build mock attributed that will mimic this behavior

Linting

Linting is very important in every programming language and even though HCL is a declarative language, linting can also ensure that your code is written correctly.

In the Terraform context, linting refers to the process of analyzing code to detect syntax errors, enforce style guidelines, and identify potential issues before doing an actual terraform plan. This step is crucial for maintaining code quality and consistency, and it enhances collaboration. Tools designed for Terraform, such as tflint, offer customizable rulesets that cater to different project needs and standards, facilitating the automation of code review processes and integration into CI pipelines.

  • Terraform fmt: This is a built-in Terraform command that should be the first port of call. The command formats your Terraform code based on a set of standard formatting rules.

Use terraform fmt -check and terraform validate to format and validate the correctness of your Terraform configuration.

  • TFLint: This is a popular open-source tool that checks for syntax errors, best practices, and code style consistency. Once installed, simply run it using the command:
tflint /path/to/terraform/code
pip install checkov
checkov -d /path/to/terraform/code

Checkov will identify security issues and provides recommendations for how to fix the issue, along with the location of the relevant code, such as publically accessible storage accounts.

  • Terrascan: This open-source tool performs static code analysis to identify security vulnerabilities and compliance violations in Terraform code. Example output is shown below for a publically accessible storage account:
=== [azure_storage_account] ===
Rules:
Risky network access configuration for storage account
Rule ID: AWS_3_2
Description: A storage account with unrestricted network access configuration may result in unauthorized access to the account and its data.
Severity: CRITICAL
Tags: [network,storage]
Status: FAILED
Resource:

Check out the list of other popular tools used in Terraform-managed deployments.

Compliance testing

terraform-compliance enables you to write a set of conditions in YAML files that must be met and test your code against them.

It can easily be installed using pip and run using the command shown below:

pip install terraform-compliance
terraform-compliance -p /path/to/policy/file.yaml -f /path/to/terraform/code

For example, the YAML file below specifies that Azure Storage Account should not be publicly accessible:

controls:
  - name: azure-storage-not-publicly-accessible
    description: Azure Storage Account should not be publicly accessible
    rules:
      - azure_storage_account:
          public_access_allowed: False

Drift testing

Terraform will natively test for drift between your code and the real infrastructure when terraform plan is run. Terraform will compare the current state of your infrastructure to the state saved in the state file.

If there are any differences, Terraform will display an execution plan that shows how to update your infrastructure to match your configuration.

You can also make use of driftctl which is a free open-source tool that can report on infrastructure drift. Example output from the tool is shown below:

Found 11 resource(s) – 73% coverage
8 managed by terraform
2 not managed by terraform
1 found in terraform state but missing on the cloud provider

Running terraform plan manually works fine for catching drift on demand, but it doesn’t scale well across multiple stacks. If you’re managing several environments, you need something that monitors continuously and alerts you when drift happens — not something you remember to run.

Spacelift’s drift detection runs on a configurable schedule across all your stacks, surfaces drift in a single dashboard, and lets you remediate directly from the UI. Setup is as simple as configuring a cron job. You can read more in the documentation.

When to use each Terraform testing strategy?

Not every project needs every type of test. The table below gives you a quick reference for deciding what to use and when.

Strategy Use it when… Skip it when…
Linting Always — every project, every pipeline
Unit testing Testing module logic, variable validation, conditional expressions Your config has no reusable modules or custom logic
Contract testing You publish modules for others to consume You only consume modules, never publish them
Integration testing Testing that multiple resources work together correctly Cost or time constraints make real infrastructure impractical
E2E testing Validating a full environment before promoting to production Early development stages where configs change frequently
Compliance testing Enforcing security policies across teams or at scale Single-developer projects with no governance requirements
Drift testing Long-lived infrastructure that may be modified outside of IaC Ephemeral environments that are always rebuilt from scratch

A good rule of thumb is to think in layers: 

       ┌───────────────┐
          │  E2E Testing  │  ← Pre-production / release gates
          ├───────────────┤
          │  Integration  │  ← PR merges / nightly
          ├───────────────┤
          │ Unit/Contract │  ← Every commit
          ├───────────────┤
          │    Linting    │  ← Pre-commit hooks
          └───────────────┘
  • Linting (terraform fmt, terraform validate, TFLint) should run as a pre-commit hook or the first step in your CI pipeline. It’s near-instant and catches the most common errors early.
  • Unit and contract tests using terraform test with command = plan create no real infrastructure, so they’re fast enough to run on every pull request.
  • Integration tests create real resources and can take minutes to hours depending on your provider. Run these on PR merges or nightly, and always destroy resources immediately afterward to keep costs down.
  • E2E tests are the most expensive and should act as a gate before promoting changes to staging or production.
  • Compliance and drift testing sit outside the standard commit/merge cycle. Compliance tests fit best as a policy enforcement step in CI. Drift detection should run on a schedule against live environments — you can read more about how Spacelift handles this in the drift detection section below.

Key points

By combining the testing strategies outlined in this article and adopting a holistic approach, you can ensure that your Terraform code is well-tested and reliable, and that changes are deployed quickly and safely.

Spacelift is designed for your whole team. Everyone works in the same space, supported by policy as code that enforces access controls, security guardrails, and compliance standards — so you can manage your infrastructure more efficiently without compromising on safety.

Read more about integrating security tools with Spacelift. And if you want to learn more about Spacelift, create a free account or book a demo with one of our engineers.

Ship infrastructure as fast as developers code

Build more complex workflows with Terraform using policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and more.

Start free trial

Frequently asked questions

  • What is the difference between terraform test and Terratest?

    terraform test is Terraform’s built-in framework (v1.6+) where tests are written in HCL. Terratest is a third-party Go-based framework from Gruntwork that offers more flexibility for complex E2E scenarios but has a steeper learning curve.

  • What is the difference between terraform plan and terraform test?

    terraform plan previews what changes Terraform would make. terraform test runs assertions against your configuration to verify it behaves as expected, and supports mocking and multi-step run blocks.

  • Can I use terraform test with OpenTofu?

    Yes. OpenTofu supports the same .tftest.hcl format via the tofu test command. It also adds a .tofutest.hcl extension for OpenTofu-specific tests, useful when maintaining modules that target both tools.

  • What version of Terraform introduced the terraform test command?

    General availability landed in Terraform v1.6.0 (October 2023). Mock providers were introduced as a beta feature in v1.7.0.

Article sources

HashiCorp Developer | Terraform Docs. Tests – Configuration Language. Accessed: 21 October 2025

HashiCorp Developer | Terraform Docs. Write Terraform Tests. Accessed: 21 October 2025

HashiCorp Blog. Testing HashiCorp Terraform. Accessed: 21 October 2025

Terraform Project Structure
Cheat Sheet

Get the Terraform file & project structure

PDF cheat sheet.

terraform files cheat sheet bottom overlay
Share your data and download the cheat sheet