Testing Terraform Code
1. Using Terraform Validate
The terraform validate command checks whether a Terraform configuration is valid and can be applied without errors. It does not interact with remote systems or resources, making it a fast way to catch syntax and configuration issues early in the development process.
| Command | Description | Options |
|---|---|---|
| terraform validate | Validates the Terraform configuration files in the current directory. | -no-color - Disables color output-json - Outputs the result in JSON format |
terraform validate does not require a backend or state file to be present, but it will report errors if required variables are missing or if there are syntax issues in the configuration files.
2. Using Terraform Test Framework
The Terraform Test Framework provides a structured way to test your Terraform configurations. It allows you to define test cases that validate the behavior of your infrastructure code.
| Component | Description | Notes |
|---|---|---|
| test block | Defines a test case within a .tf file |
Must be inside a test block in a Terraform file |
| check block | Defines assertions to validate the state of resources | Can be used multiple times within a single test |
| variables | Input variables for the test configuration | Can reference values from the main configuration |
Example: Basic Test Case
terraform {
required_version = ">= 1.5.0"
}
provider "aws" {
region = "us-west-2"
}
resource "aws_s3_bucket" "test_bucket" {
bucket = "my-test-bucket"
}
test "bucket_exists" {
providers = {
aws = aws
}
check "bucket_name" {
assert {
condition = aws_s3_bucket.test_bucket.bucket == "my-test-bucket"
error_message = "Bucket name does not match expected value"
}
}
}
Workflow for Writing Tests
- Create a
.tffile with test blocks - Define providers and resources needed for testing
- Add
checkblocks to assert desired states - Run
terraform testcommand to execute tests
3. Writing Unit Tests
Example: Test a simple data source
data "aws_ami" "latest_amazon_linux" {
most_recent = true
owners = ["137112412987"]
filters {
name = "amzn2-ami-hvm-*"
values = ["*x86_64"]
}
}
output "ami_id" {
value = data.aws_ami.latest_amazon_linux.id
}
| Test Type | Description | Tool | Notes |
|---|---|---|---|
| Data Source Test | Validate data source outputs | test block in Terraform |
Use data blocks to simulate real resources |
| Resource Test | Validate resource creation and attributes | test block in Terraform |
Use resource blocks to simulate real resources |
test block is available in version 1.6+.
Example: Using test block to validate resource creation
resource "aws_s3_bucket" "example" {
bucket = "my-example-bucket"
}
test "bucket_creation" {
providers = {
aws = aws
}
step "create_bucket" {
config = aws_s3_bucket.example
}
check "bucket_exists" {
condition = aws_s3_bucket.example.id != ""
error_message = "Bucket was not created"
}
}
4. Writing Integration Tests
Example: Basic Integration Test Structure
package integration
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
)
func TestTerraformExample(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "./example",
Vars: map[string]interface{}{
"instance_type": "t2.micro",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Assert that the instance was created
instanceId := terraform.Output(t, terraformOptions, "instance_id")
if instanceId == "" {
t.Fatal("Expected instance to be created")
}
}
| Test Type | Description | Tools | Notes |
|---|---|---|---|
| Infrastructure as Code | Testing Terraform code using real infrastructure | terratest, testinfra |
Requires provisioned infrastructure |
| State Validation | Verifying that Terraform state matches expectations | terraform plan, terraform show |
Use -out to capture plan output |
| Resource Validation | Checking that resources are created with correct attributes | terraform output, aws ec2 describe-instances |
Validate resource properties in state |
Best Practices for Integration Tests
- Use
terratestfor robust test frameworks - Test real infrastructure with mocked or stubbed services
- Validate outputs and state after each test run
- Use
defer terraform.Destroy(t, options)to clean up resources - Isolate test environments with unique names or tags
5. Using Terraform Plan Tests
Terraform provides a built-in testing framework to validate your configurations. Plan tests are used to verify that the plan output matches expectations.
| Field | Description | Example |
|---|---|---|
| plan | Specifies the path to the plan file or the plan itself. | plan = "example.tfplan" |
| expectations | Defines the expected outcomes from the plan. | expectations = ["resource_count == 2"] |
Example: Basic Plan Test
terraform {
required_version = ">= 1.0"
}
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
resource "aws_instance" "db" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
# Test block
test "plan_test" {
plan = "example.tfplan"
expectation "resource_count" {
condition = length(aws_instance) == 2
error_message = "Expected 2 instances"
}
}
terraform plan -out=example.tfplan.
Plan Test Summary
- Use
terraform plan -out=plan.tfplanto generate a plan file. - Define tests in
.tftest.hclfiles. - Each test can have multiple expectations to validate plan outcomes.
6. Implementing Policy Tests
Example: Basic Policy Test
package main
import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)
func TestAccPolicyBasic(t *testing.T) {
resource.Test(t, resource.TestCase{
Providers: testProviders,
Steps: []resource.TestStep{
{
Config: testConfigBasic,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("aws_instance.test", "ami", "ami-12345678"),
),
},
},
})
}
| Property | Description | Values | Notes |
|---|---|---|---|
| resource.Test | Function to run a test case | func(t *testing.T, testCase resource.TestCase) |
Core testing function for Terraform resources |
| Providers | Map of providers to use in tests | map[string]terraform.ResourceProvider |
Must include all providers used in config |
| Steps | List of test steps to run | []resource.TestStep |
Each step can have Config, Check, and Destroy |
| Config | Configuration to apply in this test step | string |
Can use HCL or JSON format |
| Check | Assertions to run after applying config | resource.TestCheckFunc |
Use built-in checks or custom functions |
Key Concepts
- Test Framework: Uses Go's testing package with Terraform-specific helpers
- Resource Testing: Validates that resources are created with expected attributes
- State Management: Tests can check state after resource creation or destruction
- Provider Isolation: Each test uses its own provider configuration to avoid interference
7. Using Mock Providers
Mock providers in Terraform allow you to simulate the behavior of real providers during testing, enabling faster and more reliable test runs without requiring actual cloud resources or external dependencies.
| Property | Description | Example |
|---|---|---|
| mock_provider | Defines a mock provider configuration to simulate real provider behavior. | mock_provider "aws" { } |
| mock_resource | Represents a mocked resource that mimics the structure of a real resource. | mock_resource "aws_instance" { } |
Example: Defining a Mock Provider
provider "aws" {
region = "us-west-2"
mock_provider "aws" {
# Mock configuration
}
}
Example: Using Mock Resources
resource "aws_instance" "mocked_instance" {
mock_resource "aws_instance" {
# Mocked resource definition
}
}
8. Testing Module Outputs
| Property | Description | Example |
|---|---|---|
| output.name | Name of the output variable | instance_id |
| output.value | Value of the output variable | aws_instance.example.id |
| output.sensitive | Boolean indicating if the output is sensitive | true |
Example: Define and Test Module Output
output "instance_id" {
value = aws_instance.example.id
sensitive = false
}
output "private_ip" {
value = aws_instance.example.private_ip
sensitive = true
}
Example: Test Output in Terraform
data "terraform_remote_state" "prod" {
backend = "local"
config = {
path = "../prod/terraform.tfstate"
}
}
output "test_output" {
value = data.terraform_remote_state.prod.outputs.instance_id
}
9. Implementing End-to-End Tests
Example: Basic E2E Test Setup
#!/bin/bash
# test.sh
set -e
# Initialize Terraform
terraform init -backend=false
# Plan and apply
terraform plan -out=tfplan
terraform apply -auto-approve tfplan
# Validate deployment
echo "Validating deployment..."
| Test Type | Description | Tools | Notes |
|---|---|---|---|
| Infrastructure Validation | Ensures deployed infrastructure matches expected state | terraform show, terraform plan |
Use -out to capture plan for validation |
| State Consistency | Verifies that state file reflects actual infrastructure | terraform refresh, terraform state list |
Check for drift between desired and actual state |
| Integration Testing | Tests interaction between multiple resources | testinfra, inspec |
Validate connectivity, access control, and behavior |
Workflow: E2E Test Execution
- Initialize Terraform with clean state
- Apply infrastructure changes using
terraform apply - Run validation checks against deployed resources
- Clean up infrastructure after test completion
10. Automating Test Runs
Example: CI/CD Pipeline with Terraform Tests
name: Terraform CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Run Terraform Init
run: terraform init
- name: Run Terraform Validate
run: terraform validate
- name: Run Terraform Test
run: terraform test
| Step | Description | Command | Notes |
|---|---|---|---|
| Checkout Code | Retrieve source code from repository | actions/checkout@v3 |
Required for all CI steps |
| Setup Terraform | Install Terraform CLI | hashicorp/setup-terraform@v2 |
Ensures correct version |
| Init | Initialize Terraform working directory | terraform init |
Downloads required providers |
| Validate | Check configuration syntax | terraform validate |
Catches configuration errors early |
| Test | Execute test suite | terraform test |
Runs all defined tests |