Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
hashicorp avatar

Terraform Test

  • 6.1k installs
  • 781 repo stars
  • Updated August 4, 2026
  • hashicorp/agent-skills

Terraform's native testing framework that validates infrastructure configuration updates through test files (.tftest.hcl) containing run blocks and assertions, protecting existing resources by running against temporary i

About

Terraform Test provides a built-in framework for validating infrastructure-as-code configurations through plan-mode unit tests and apply-mode integration tests. Developers use it to catch breaking changes before deployment, test module outputs and resource behavior, mock external dependencies, and validate conditional logic. Core workflows include writing .tftest.hcl files with run blocks (test scenarios), defining assert blocks for validation, using mock providers to avoid credentials, and organizing tests by type (unit vs integration) for efficient CI/CD execution. Tests run against temporary resources and clean up automatically, protecting existing state. --- name: terraform-test description: Comprehensive guide for writing and running Terraform tests. Use when creating test files (.tftest.hcl), writing test scenarios with run blocks, validating infrastructure behavior with assertions, mocking providers and data sources, testing module outputs and resource configurations, or troubleshooting Terraform test syntax and execution. metadata: copyright: Copyright IBM Corp.

  • Plan-mode unit tests execute without creating real resources - fast feedback loop
  • Mock provider support (Terraform 1.7.0+) eliminates credential requirements for testing
  • Sequential and parallel test execution with state isolation via state_key
  • Assert blocks validate outputs, resource counts, tags, and conditional logic with clear error messages
  • Automatic cleanup destroys test resources in reverse order; skip with -no-cleanup for debugging

Terraform Test by the numbers

  • 6,058 all-time installs (skills.sh)
  • +235 installs in the week ending Aug 5, 2026 (Skillselion tracking)
  • Ranked #258 of 2,153 Testing & QA skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

terraform-test capabilities & compatibility

Capabilities
write .tftest.hcl test files with run blocks · define assert conditions for validation · mock external providers and data sources · test module outputs and resource configurations · validate conditional resource creation · run unit and integration tests separately · debug test failures with verbose output
Works with
github · gitlab · azure devops
Use cases
testing · ci cd · code review
Platforms
macOS · Windows · Linux
Runs
Runs locally
Pricing
Free
From the docs

What terraform-test says it does

Terraform's built-in testing framework validates that configuration updates don't introduce breaking changes. Tests run against temporary resources, protecting existing infrastructure and state files.
SKILL.md
npx skills add https://github.com/hashicorp/agent-skills --skill terraform-test

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs6.1k
repo stars781
Security audit3 / 3 scanners passed
Last updatedAugust 4, 2026
Repositoryhashicorp/agent-skills

What it does

Write and run Terraform tests to validate infrastructure configuration changes without affecting live resources.

Who is it for?

Module authors, infrastructure teams, and developers validating Terraform configurations in CI/CD pipelines before merge or deployment.

Skip if: Testing non-Terraform infrastructure code; load or performance testing; testing credentials or secrets (use mocks instead).

When should I use this skill?

Creating .tftest.hcl test files, writing test scenarios with run blocks, validating module outputs and resource behavior, mocking providers, testing conditional logic, or troubleshooting test syntax and execution.

What you get

Developers write plan-mode unit tests for fast validation and apply-mode integration tests for real behavior verification, catching breaking changes before deployment with automatic cleanup.

  • GitHub Actions workflow YAML
  • terraform test execution reports

By the numbers

  • Terraform 1.7.0+ required for mock provider support
  • Tests support plan and apply modes; resources destroyed in reverse run block order
  • Test files named *_unit_test.tftest.hcl and *_integration_test.tftest.hcl for CI filtering

Files

SKILL.mdMarkdownGitHub ↗

Terraform Test

Terraform's built-in testing framework validates that configuration updates don't introduce breaking changes. Tests run against temporary resources, protecting existing infrastructure and state files.

Reference Files

  • references/MOCK_PROVIDERS.md — Mock provider syntax, common defaults, when to use mocks (Terraform 1.7.0+ only — skip if the user's version is below 1.7)
  • references/CI_CD.md — GitHub Actions and GitLab CI pipeline examples
  • references/EXAMPLES.md — Complete example test suite (unit, integration, and mock tests for a VPC module)

Read the relevant reference file when the user asks about mocking, CI/CD integration, or wants a full example.

Core Concepts

  • Test file (.tftest.hcl / .tftest.json): Contains run blocks that validate your configuration
  • Run block: A single test scenario with optional variables, providers, and assertions
  • Assert block: Conditions that must be true for the test to pass
  • Mock provider: Simulates provider behavior without real infrastructure (Terraform 1.7.0+)
  • Test modes: apply (default, creates real resources) or plan (validates logic only)

File Structure

my-module/
├── main.tf
├── variables.tf
├── outputs.tf
└── tests/
    ├── defaults_unit_test.tftest.hcl         # plan mode — fast, no resources
    ├── validation_unit_test.tftest.hcl        # plan mode
    └── full_stack_integration_test.tftest.hcl # apply mode — creates real resources

Use *_unit_test.tftest.hcl for plan-mode tests and *_integration_test.tftest.hcl for apply-mode tests so they can be filtered separately in CI.

Test File Structure

# Optional: test-wide settings
test {
  parallel = true  # Enable parallel execution for all run blocks (default: false)
}

# Optional: file-level variables (highest precedence, override all other sources)
variables {
  aws_region    = "us-west-2"
  instance_type = "t2.micro"
}

# Optional: provider configuration
provider "aws" {
  region = var.aws_region
}

# Required: at least one run block
run "test_default_configuration" {
  command = plan

  assert {
    condition     = aws_instance.example.instance_type == "t2.micro"
    error_message = "Instance type should be t2.micro by default"
  }
}

Run Block

run "test_name" {
  command  = plan  # or apply (default)
  parallel = true  # optional, since v1.9.0

  # Override file-level variables
  variables {
    instance_type = "t3.large"
  }

  # Reference a specific module
  module {
    source  = "./modules/vpc"  # local or registry only (not git/http)
    version = "5.0.0"          # registry modules only
  }

  # Control state isolation
  state_key = "shared_state"  # since v1.9.0

  # Plan behavior
  plan_options {
    mode    = refresh-only  # or normal (default)
    refresh = true
    replace = [aws_instance.example]
    target  = [aws_instance.example]
  }

  # Assertions
  assert {
    condition     = aws_instance.example.id != ""
    error_message = "Instance should have a valid ID"
  }

  # Expected failures (test passes if these fail)
  expect_failures = [
    var.instance_count
  ]
}

Common Test Patterns

Validate outputs

run "test_outputs" {
  command = plan

  assert {
    condition     = output.vpc_id != null
    error_message = "VPC ID output must be defined"
  }

  assert {
    condition     = can(regex("^vpc-", output.vpc_id))
    error_message = "VPC ID should start with 'vpc-'"
  }
}

Conditional resources

run "test_nat_gateway_disabled" {
  command = plan

  variables {
    create_nat_gateway = false
  }

  assert {
    condition     = length(aws_nat_gateway.main) == 0
    error_message = "NAT gateway should not be created when disabled"
  }
}

Resource counts

run "test_resource_count" {
  command = plan

  variables {
    instance_count = 3
  }

  assert {
    condition     = length(aws_instance.workers) == 3
    error_message = "Should create exactly 3 worker instances"
  }
}

Tags

run "test_resource_tags" {
  command = plan

  variables {
    common_tags = {
      Environment = "production"
      ManagedBy   = "Terraform"
    }
  }

  assert {
    condition     = aws_instance.example.tags["Environment"] == "production"
    error_message = "Environment tag should be set correctly"
  }

  assert {
    condition     = aws_instance.example.tags["ManagedBy"] == "Terraform"
    error_message = "ManagedBy tag should be set correctly"
  }
}

Data sources

run "test_data_source_lookup" {
  command = plan

  assert {
    condition     = data.aws_ami.ubuntu.id != ""
    error_message = "Should find a valid Ubuntu AMI"
  }

  assert {
    condition     = can(regex("^ami-", data.aws_ami.ubuntu.id))
    error_message = "AMI ID should be in correct format"
  }
}

Validation rules

run "test_invalid_environment" {
  command = plan

  variables {
    environment = "invalid"
  }

  expect_failures = [
    var.environment
  ]
}

Sequential tests with dependencies

run "setup_vpc" {
  command = apply

  assert {
    condition     = output.vpc_id != ""
    error_message = "VPC should be created"
  }
}

run "test_subnet_in_vpc" {
  command = plan

  variables {
    vpc_id = run.setup_vpc.vpc_id
  }

  assert {
    condition     = aws_subnet.example.vpc_id == run.setup_vpc.vpc_id
    error_message = "Subnet should be in the VPC from setup_vpc"
  }
}

Plan options (refresh-only, targeted)

run "test_refresh_only" {
  command = plan

  plan_options {
    mode = refresh-only
  }

  assert {
    condition     = aws_instance.example.tags["Environment"] == "production"
    error_message = "Tags should be refreshed correctly"
  }
}

run "test_specific_resource" {
  command = plan

  plan_options {
    target = [aws_instance.example]
  }

  assert {
    condition     = aws_instance.example.instance_type == "t2.micro"
    error_message = "Targeted resource should be planned"
  }
}

Parallel modules

run "test_networking_module" {
  command  = plan
  parallel = true

  module {
    source = "./modules/networking"
  }

  assert {
    condition     = output.vpc_id != ""
    error_message = "VPC should be created"
  }
}

run "test_compute_module" {
  command  = plan
  parallel = true

  module {
    source = "./modules/compute"
  }

  assert {
    condition     = output.instance_id != ""
    error_message = "Instance should be created"
  }
}

State key sharing

run "create_foundation" {
  command   = apply
  state_key = "foundation"

  assert {
    condition     = aws_vpc.main.id != ""
    error_message = "Foundation VPC should be created"
  }
}

run "create_application" {
  command   = apply
  state_key = "foundation"

  variables {
    vpc_id = run.create_foundation.vpc_id
  }

  assert {
    condition     = aws_instance.app.vpc_id == run.create_foundation.vpc_id
    error_message = "Application should use foundation VPC"
  }
}

Cleanup ordering (S3 objects before bucket)

run "create_bucket" {
  command = apply

  assert {
    condition     = aws_s3_bucket.example.id != ""
    error_message = "Bucket should be created"
  }
}

run "add_objects" {
  command = apply

  assert {
    condition     = length(aws_s3_object.files) > 0
    error_message = "Objects should be added"
  }
}

# Cleanup destroys in reverse: objects first, then bucket

Multiple aliased providers

provider "aws" {
  alias  = "primary"
  region = "us-west-2"
}

provider "aws" {
  alias  = "secondary"
  region = "us-east-1"
}

run "test_with_specific_provider" {
  command = plan

  providers = {
    aws = provider.aws.secondary
  }

  assert {
    condition     = aws_instance.example.availability_zone == "us-east-1a"
    error_message = "Instance should be in us-east-1 region"
  }
}

Complex conditions

assert {
  condition = alltrue([
    for subnet in aws_subnet.private :
    can(regex("^10\\.0\\.", subnet.cidr_block))
  ])
  error_message = "All private subnets should use 10.0.0.0/8 CIDR range"
}

Cleanup

Resources are destroyed in reverse run block order after test completion. This matters for dependencies (e.g., S3 objects before bucket). Use terraform test -no-cleanup to skip cleanup for debugging.

Running Tests

terraform test                                        # all tests
terraform test tests/defaults.tftest.hcl             # specific file
terraform test -filter=test_vpc_configuration        # by run block name
terraform test -test-directory=integration-tests     # custom directory
terraform test -verbose                              # detailed output
terraform test -no-cleanup                           # skip resource cleanup

Best Practices

1. Naming: *_unit_test.tftest.hcl for plan mode, *_integration_test.tftest.hcl for apply mode 2. Test naming: Use descriptive run block names that explain the scenario being tested 3. Default to plan: Use command = plan unless you need to test real resource behavior 4. Use mocks for external dependencies — faster and no credentials needed (see references/MOCK_PROVIDERS.md) 5. Error messages: Make them specific enough to diagnose failures without running the test again 6. Negative tests: Use expect_failures to verify validation rules reject bad inputs 7. Variable coverage: Test different variable combinations to validate all code paths — test variables have the highest precedence and override all other sources 8. Module sources: Test files only support local paths and registry modules — not git or HTTP URLs 9. Parallel execution: Use parallel = true for independent tests with different state files 10. Cleanup: Integration tests destroy resources in reverse run block order automatically; use -no-cleanup for debugging 11. CI/CD: Run unit tests on every PR, integration tests on merge (see references/CI_CD.md)

Troubleshooting

IssueSolution
Assertion failuresUse -verbose to see actual vs expected values
Missing credentialsUse mock providers for unit tests
Unsupported module sourceConvert git/HTTP sources to local modules
Tests interferingUse state_key or separate modules for isolation
Slow testsUse command = plan and mocks; run integration tests separately

References

Related skills

Forks & variants (1)

Terraform Test has 1 known copy in the catalog totaling 35 installs. They canonicalize to this original listing.

How it compares

Pick terraform-test for native terraform test CI stages; use generic Terraform lint skills when you only need fmt and validate without test filters.

FAQ

What is the difference between plan and apply mode in Terraform tests?

Plan mode (command = plan) validates logic without creating real resources - fast and safe for unit tests. Apply mode (default) creates temporary resources to test actual behavior - slower but catches real-world issues. Use plan for unit tests, apply for integration tests.

How do I test that a resource is not created based on a variable?

Use assert blocks to check length: assert { condition = length(aws_nat_gateway.main) == 0, error_message = "NAT gateway should not exist" }. Or use expect_failures to verify validation rules reject bad inputs.

How do I avoid credential issues when running tests?

Use mock providers (Terraform 1.7.0+) which simulate provider behavior without real infrastructure or credentials. Separate unit tests (with mocks, plan mode) from integration tests (real resources, apply mode) and run mocks in CI on every PR.

Is Terraform Test safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

Testing & QAtestingdevops

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.