Terraform Test
HashiCorpOfficialPublished by HashiCorp
- Skill
- Tool integration
- Cloud and DevOps
- MPL-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to using Terraform's native testing framework. It covers the creation of .tftest.hcl files, the use of run and assert blocks for validation, and strategies for both plan-mode unit testing and apply-mode integration testing. It also details how to utilize mock providers to simulate infrastructure without requiring cloud credentials, and how to integrate these tests into CI/CD pipelines.
What it does
- Defines test scenarios using run and assert blocks within .tftest.hcl files.
- Supports both plan-mode unit tests and apply-mode integration tests.
- Enables mocking of providers and data sources for faster, credential-free testing.
- Provides patterns for validating outputs, resource counts, and conditional logic.
- Offers guidance on CI/CD integration for automated testing workflows.
- Explains state isolation and cleanup procedures for infrastructure tests.
When to use it
- When you need to validate that configuration changes do not introduce breaking changes.
- When you want to implement unit testing for Terraform modules without deploying real resources.
- When you need to perform integration testing to verify infrastructure behavior in a live environment.
- When you need to mock external dependencies to speed up test execution.
- When setting up automated testing pipelines in GitHub Actions or GitLab CI.
Advantages
- Native integration with Terraform CLI.
- Supports rapid feedback loops via plan-mode testing.
- Reduces risk by validating infrastructure logic before deployment.
- Simplifies testing through built-in mocking capabilities.
- Enables consistent testing standards across team modules.
Usage examples
- “How do I write a basic plan-mode test for my Terraform module?”
- The agent will provide a template for a .tftest.hcl file using a run block with command = plan and an assert block to verify specific resource attributes.
- “How can I test my infrastructure without needing AWS credentials?”
- The agent will explain how to use the mock_provider block to simulate provider behavior, allowing you to run tests in plan mode without real cloud access.
- “What is the best way to organize my test files?”
- The agent will recommend a directory structure using *_unit_test.tftest.hcl for plan-mode tests and *_integration_test.tftest.hcl for apply-mode tests.
- “How do I run only my unit tests in a CI pipeline?”
- The agent will provide the command terraform test -filter=unit_test and show how to map this to your CI/CD configuration files.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/hashicorp/terraform-test/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip terraform-test.zip -d .agents/skills
Documentation by HashiCorp
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 examplesreferences/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): Containsrunblocks that validate your configurationRun 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) orplan(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 resourcesUse *_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 bucketMultiple 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 cleanupBest Practices
Naming:
*_unit_test.tftest.hclfor plan mode,*_integration_test.tftest.hclfor apply modeTest naming: Use descriptive run block names that explain the scenario being tested
Default to plan: Use
command = planunless you need to test real resource behaviorUse mocks for external dependencies — faster and no credentials needed (see
references/MOCK_PROVIDERS.md)Error messages: Make them specific enough to diagnose failures without running the test again
Negative tests: Use
expect_failuresto verify validation rules reject bad inputsVariable coverage: Test different variable combinations to validate all code paths — test variables have the highest precedence and override all other sources
Module sources: Test files only support local paths and registry modules — not git or HTTP URLs
Parallel execution: Use
parallel = truefor independent tests with different state filesCleanup: Integration tests destroy resources in reverse run block order automatically; use
-no-cleanupfor debuggingCI/CD: Run unit tests on every PR, integration tests on merge (see
references/CI_CD.md)
Troubleshooting
| Issue | Solution |
|---|---|
| Assertion failures | Use -verbose to see actual vs expected values |
| Missing credentials | Use mock providers for unit tests |
| Unsupported module source | Convert git/HTTP sources to local modules |
| Tests interfering | Use state_key or separate modules for isolation |
| Slow tests | Use command = plan and mocks; run integration tests separately |
References
Files
- ATTRIBUTION.mdAdded by AutoGineer896 B
- LICENSELicence file added by AutoGineer16.3 KiB
- SKILL.md10.9 KiB
- references/CI_CD.md1.9 KiB
- references/EXAMPLES.md6.5 KiB
- references/MOCK_PROVIDERS.md4.4 KiB
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs a command-line tool
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- HashiCorp
- Publisher
- HashiCorp (Official)
- Source
- hashicorp/agent-skills · plugins/terraform/skills/terraform-test · commit 5e30fa5
- Licence
- Mozilla Public License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by HashiCorp.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download terraform-test.zip