Terraform’s built-in testing framework allows you to assert the state of your infrastructure after a Terraform run, but it’s not about validating your Terraform code itself; it’s about validating the results of your Terraform code.

Let’s see it in action. Imagine you’re provisioning an AWS S3 bucket and want to ensure it’s created with specific public access block settings.

# main.tf
resource "aws_s3_bucket" "example" {
  bucket = "my-unique-test-bucket-12345"
  acl    = "private"

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# test/s3_bucket_test.go
package main

import (
	"testing"

	"github.com/gruntwork-io/terratest/modules/terraform"
)

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

	// Define the Terraform options. This specifies the directory containing
	// your Terraform configuration.
	terraformOptions := &terraform.Options{
		TerraformDir: "../", // Assumes main.tf is in the parent directory
	}

	// Defer the destruction of the resources to ensure cleanup.
	defer terraform.Destroy(t, terraformOptions)

	// Apply the Terraform configuration.
	terraform.InitAndApply(t, terraformOptions)

	// Assert that the S3 bucket has the correct public access block settings.
	// This is where the testing framework shines – it queries the *actual*
	// state of the provisioned resource.
	actualBucketConfig := terraform.GetS3BucketPublicAccess(t, "my-unique-test-bucket-12345")

	assert.True(t, actualBucketConfig.BlockPublicAcls)
	assert.True(t, actualBucketConfig.BlockPublicPolicy)
	assert.True(t, actualBucketConfig.IgnorePublicAcls)
	assert.True(t, actualBucketConfig.RestrictPublicBuckets)
}

This example demonstrates the core idea: write your infrastructure code, then write a Go test that applies that code and then verifies the resulting infrastructure’s state against your expectations. The terratest library, which is what most people use for Terraform testing, provides functions to interact with cloud providers after Terraform has done its job.

The problem Terraform testing solves is the "trust but verify" dilemma for infrastructure as code. You write your main.tf and assume it does what you intend. But how do you know? Did a typo slip into a parameter? Did a default value change in the provider? Did an adjacent resource interfere? Terraform testing lets you write automated checks that run against the real deployed infrastructure, catching these discrepancies before they cause real-world problems.

Internally, the terratest library works by orchestrating Terraform commands (init, apply, destroy) and then using cloud provider SDKs (like AWS SDK for Go) to query the state of the resources that Terraform just provisioned. The Go test code acts as a control plane, telling Terraform what to do and then interrogating the results. You control the scope of your tests by what resources you define in your Terraform code and what assertions you write in your Go tests. You can test individual resources, groups of resources, or even entire environments.

The most surprising thing about Terraform testing is that it’s not inherently tied to the terraform test command itself, which is a newer, more opinionated feature in Terraform CLI. Most of the established ecosystem and robust testing practices revolve around external testing frameworks like terratest, which leverage the standard terraform apply and terraform destroy commands. This means you can write tests that are decoupled from specific Terraform CLI versions and benefit from the full power of Go’s testing ecosystem.

When you run go test in the directory containing your test/s3_bucket_test.go file, terratest will first execute terraform init and then terraform apply using the configuration in ../. If the apply is successful, it then proceeds to execute the assertions within your Go test function. If any of the assert.True calls fail, the go test command will report a test failure. Finally, terraform destroy is executed to clean up the provisioned resources, regardless of whether the test passed or failed.

The next logical step after validating resource state is to test the interdependencies and behaviors between multiple resources.

Want structured learning?

Take the full Terraform course →