Terraform

Integration Testing with Terraform: Ensure Infrastructure Reliability Before Deployment

AGAnurag Gupta04 Apr 2025 Β· Updated 04 Oct 2026 Β· 8 min read
Integration Testing with Terraform: Ensure Infrastructure Reliability Before Deployment

Quick answer: Integration testing in Terraform means actually deploying your configuration to a real (usually temporary) cloud environment, checking that the infrastructure behaves as expected β€” the instance answers on port 22, the load balancer returns HTTP 200 β€” and then destroying everything. The two main approaches today are Terraform’s built-in terraform test command (available since Terraform 1.6) and Terratest, a Go library from Gruntwork.

terraform plan tells you what Terraform intends to do. It cannot tell you that the security group you wrote actually allows traffic, or that the IAM policy you attached really grants the permission. Integration tests close that gap. In this guide you will learn how the testing pyramid applies to infrastructure, how to write tests with both terraform test and Terratest, how to keep cloud costs under control, and which mistakes make test suites flaky and expensive.

The infrastructure testing pyramid

Not every check needs a live cloud account. Arrange your tests in layers from cheap to expensive:

Level Tool Deploys real resources? Typical runtime
Static analysis terraform validate, TFLint, Checkov No Seconds
Unit tests terraform test with command = plan No (plan only) Seconds to a minute
Integration tests terraform test with command = apply, Terratest Yes, then destroyed 5–20 minutes
End-to-end tests Terratest plus HTTP/SSH checks, smoke tests in staging Yes 20+ minutes

Run the cheap layers on every commit and the expensive layers on pull requests or nightly. Getting the fundamentals right in Terraform Core Concepts and Syntax makes every layer above easier, because well-structured modules with clear outputs are far simpler to test.

What makes a module testable

Integration tests assert on outputs. If your module creates an EC2 instance but never outputs its public IP, the test has nothing to connect to. Before writing a single test, make sure the module under test:

  • Accepts every environment-specific value (region, name prefix, CIDR) as an input variable, so the test can inject unique values.
  • Exposes outputs for anything you want to verify: IDs, ARNs, DNS names, IP addresses.
  • Has no hard-coded names that would collide when two test runs happen at the same time.

Here is the small module we will test throughout this article:

# modules/web/main.tf
variable "name" {
  type = string
}

variable "instance_type" {
  type    = string
  default = "t3.micro"

  validation {
    condition     = startswith(var.instance_type, "t3.")
    error_message = "Only burstable t3 instance types are allowed in this module."
  }
}

data "aws_ami" "al2023" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  ami                         = data.aws_ami.al2023.id
  instance_type               = var.instance_type
  associate_public_ip_address = true
  user_data = <<-EOF
    #!/bin/bash
    dnf install -y nginx && systemctl enable --now nginx
  EOF
  tags = { Name = var.name }
}

output "instance_id" { value = aws_instance.web.id }
output "public_ip"   { value = aws_instance.web.public_ip }

Hands-on: native tests with terraform test

Since Terraform 1.6, you can write tests in HCL itself β€” no Go, Ruby or PowerShell required. Test files live in a tests/ directory and end in .tftest.hcl. Each run block executes a plan or apply and evaluates assert conditions.

# tests/web.tftest.hcl
provider "aws" {
  region = "ap-south-1"
}

variables {
  name = "tkh-test-web"
}

# Unit-style: plan only, no resources created
run "rejects_large_instances" {
  command = plan

  variables {
    instance_type = "m5.24xlarge"
  }

  expect_failures = [var.instance_type]
}

# Integration: really deploys, asserts, then destroys
run "creates_reachable_instance" {
  command = apply

  assert {
    condition     = aws_instance.web.public_ip != ""
    error_message = "Instance must receive a public IP"
  }

  assert {
    condition     = aws_instance.web.tags["Name"] == "tkh-test-web"
    error_message = "Name tag was not applied"
  }
}

The first run block relies on the validation block in the module’s variable definition and confirms that Terraform correctly refuses the bad value. The second block applies for real. Run it with:

terraform init
terraform test
# tests/web.tftest.hcl... pass
#   run "rejects_large_instances"... pass
#   run "creates_reachable_instance"... pass

Terraform automatically destroys everything created by apply runs when the file finishes, even if an assertion fails. Native tests are perfect for verifying attributes that Terraform already knows about. What they cannot do on their own is make an HTTP request to the new server β€” for that you need a general-purpose language.

Hands-on: behavioural tests with Terratest

Terratest is a Go library that wraps the Terraform CLI and adds helpers for HTTP, SSH, AWS, Azure, GCP and Kubernetes. The pattern is always the same: InitAndApply, read outputs, assert against the real world, defer Destroy.

// test/web_test.go
package test

import (
	"fmt"
	"testing"
	"time"

	http_helper "github.com/gruntwork-io/terratest/modules/http-helper"
	"github.com/gruntwork-io/terratest/modules/random"
	"github.com/gruntwork-io/terratest/modules/terraform"
	"github.com/stretchr/testify/assert"
)

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

	uniqueName := fmt.Sprintf("tkh-test-%s", random.UniqueId())

	opts := &terraform.Options{
		TerraformDir: "../modules/web",
		Vars: map[string]interface{}{
			"name": uniqueName,
		},
		EnvVars: map[string]string{"AWS_DEFAULT_REGION": "ap-south-1"},
	}

	defer terraform.Destroy(t, opts)
	terraform.InitAndApply(t, opts)

	publicIP := terraform.Output(t, opts, "public_ip")
	assert.NotEmpty(t, publicIP)

	// nginx needs a minute to install; retry up to 30 times, 10s apart
	url := fmt.Sprintf("http://%s", publicIP)
	http_helper.HttpGetWithRetry(t, url, nil, 200, "", 30, 10*time.Second)
}

Run it from the test/ directory with go mod init once, then:

go mod tidy
go test -v -timeout 30m ./...

Notice the 30-minute timeout: Go’s default of 10 minutes is often too short for cloud provisioning. The defer statement guarantees cleanup even when an assertion panics, and random.UniqueId() lets several developers run the suite simultaneously without name collisions.

Where Kitchen-Terraform and Pester fit today

Older articles recommend Kitchen-Terraform (Ruby, built on Test Kitchen and InSpec) and Pester (PowerShell). Both still work, but their communities have shrunk. Kitchen-Terraform makes sense if your organisation already runs Chef and InSpec compliance profiles; Pester makes sense for Windows-heavy teams who want to verify Active Directory or IIS configuration after Terraform provisions the VM. For a new project, start with terraform test and add Terratest when you need behavioural checks.

Seven integration-testing mistakes to avoid

  1. Testing against production or a shared dev account. Always use a dedicated sandbox account or subscription with strict budget alerts. Tests create and destroy real resources.
  2. Hard-coded resource names. Two concurrent runs both try to create tkh-test-bucket; one fails with a conflict. Always inject a random suffix.
  3. No cleanup on failure. Forgetting defer terraform.Destroy means orphaned instances billing you for weeks. Add a scheduled cleanup job (for example cloud-nuke or an AWS Lambda) as a safety net.
  4. Asserting immediately after apply. DNS propagation, instance boot and package installation take time. Use retry helpers with a sensible timeout instead of sleep.
  5. Testing the cloud provider instead of your code. Asserting that an S3 bucket exists after aws_s3_bucket is applied only proves AWS works. Test your logic: naming, tagging, policies, connectivity.
  6. Running integration tests on every commit. They are slow and cost money. Run static checks and plan-only tests on commits; run apply-based tests on PRs to main or nightly.
  7. Ignoring dependency ordering. If your test reads an output before the resource that produces it is ready, you get flaky failures. The patterns in Understanding Resource Dependencies and Ordering in Terraform apply to test code too.

Frequently asked questions

Do integration tests cost money?

Yes, but usually very little if resources are destroyed promptly. A t3.micro running for ten minutes costs a fraction of a rupee. Set a budget alarm on the test account anyway.

Can I use terraform test without applying anything?

Yes. Set command = plan in a run block. This validates variable validations, conditions and computed values without touching the cloud, which makes it fast enough to run on every commit.

Do I need to learn Go for Terratest?

Basic Go is enough. Most Terratest files follow the same twenty-line template shown above. Knowing how to read a map, call a function and write an if statement covers ninety percent of real test suites.

How do I test modules that need existing infrastructure, such as a VPC?

Either create the prerequisites in a setup run block (native tests support helper modules via module { source = "./tests/setup" }) or in Terratest apply a fixture directory first and pass its outputs as variables to the module under test.

Key takeaways

  • Integration tests deploy real infrastructure, verify behaviour and destroy it β€” plan alone is not enough.
  • Use terraform test (1.6+) for attribute assertions in pure HCL; add Terratest when you need HTTP, SSH or SDK checks.
  • Isolate tests with a sandbox account and random names; always guarantee cleanup.
  • Run cheap checks on every commit and expensive apply-based tests on PRs or nightly.

Want to build Terraform modules that ship with automated tests and CI pipelines, the way platform teams do in production? Our DevOps course walks you through Terraform, testing and pipelines with live projects and placement assistance. You can also watch step-by-step demos on our YouTube channel.

AG
Written byAnurag Gupta

Part of the Techknowledgehub team of industry mentors, writing practical guides to help you build a job-ready tech career.

More articles by Anurag Gupta β†’
Keep reading

Related articles

Leave a Reply