
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)
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
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.
npx skills add https://github.com/hashicorp/agent-skills --skill terraform-testAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6.1k |
|---|---|
| repo stars | ★ 781 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | hashicorp/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
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 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) 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
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
| 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
CI/CD Integration
GitHub Actions
name: Terraform Tests
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
- run: terraform fmt -check -recursive
- run: terraform init
- run: terraform validate
- name: Run unit tests (plan mode, no credentials needed)
run: terraform test -filter=unit_test -verbose
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
- run: terraform init
- name: Run integration tests
run: terraform test -filter=integration_test -verbose
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}GitLab CI
stages:
- validate
- test
terraform-unit-tests:
image: hashicorp/terraform:1.9
stage: validate
before_script:
- terraform init
script:
- terraform fmt -check -recursive
- terraform validate
- terraform test -filter=unit_test -verbose
terraform-integration-tests:
image: hashicorp/terraform:1.9
stage: test
before_script:
- terraform init
script:
- terraform test -filter=integration_test -verbose
only:
- mainRecommended CI Strategy
- Run unit tests (plan mode + mock tests) on every PR — fast, no credentials needed
- Run integration tests only on merge to main or nightly — requires cloud credentials
- Use
-filter=unit_test/-filter=integration_testto separate test types based on naming convention - Store cloud credentials as CI secrets, never in code
Example Test Suite
Complete example testing a VPC module with unit, integration, and mock tests.
Unit Tests (Plan Mode)
# tests/vpc_module_unit_test.tftest.hcl
variables {
environment = "test"
aws_region = "us-west-2"
}
run "test_defaults" {
command = plan
variables {
vpc_cidr = "10.0.0.0/16"
vpc_name = "test-vpc"
}
assert {
condition = aws_vpc.main.cidr_block == "10.0.0.0/16"
error_message = "VPC CIDR should match input"
}
assert {
condition = aws_vpc.main.enable_dns_hostnames == true
error_message = "DNS hostnames should be enabled by default"
}
assert {
condition = aws_vpc.main.tags["Name"] == "test-vpc"
error_message = "VPC name tag should match input"
}
}
run "test_subnets" {
command = plan
variables {
vpc_cidr = "10.0.0.0/16"
vpc_name = "test-vpc"
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.10.0/24", "10.0.11.0/24"]
}
assert {
condition = length(aws_subnet.public) == 2
error_message = "Should create 2 public subnets"
}
assert {
condition = length(aws_subnet.private) == 2
error_message = "Should create 2 private subnets"
}
assert {
condition = alltrue([
for subnet in aws_subnet.private :
subnet.map_public_ip_on_launch == false
])
error_message = "Private subnets should not assign public IPs"
}
}
run "test_outputs" {
command = plan
variables {
vpc_cidr = "10.0.0.0/16"
vpc_name = "test-vpc"
}
assert {
condition = output.vpc_id != ""
error_message = "VPC ID output should not be empty"
}
assert {
condition = can(regex("^vpc-", output.vpc_id))
error_message = "VPC ID should have correct format"
}
assert {
condition = output.vpc_cidr == "10.0.0.0/16"
error_message = "VPC CIDR output should match input"
}
}
run "test_invalid_cidr" {
command = plan
variables {
vpc_cidr = "invalid"
vpc_name = "test-vpc"
}
expect_failures = [
var.vpc_cidr
]
}Integration Tests (Apply Mode)
# tests/vpc_module_integration_test.tftest.hcl
variables {
environment = "integration-test"
aws_region = "us-west-2"
}
run "integration_test_vpc_creation" {
# command defaults to apply — creates real AWS resources
variables {
vpc_cidr = "10.100.0.0/16"
vpc_name = "integration-test-vpc"
}
assert {
condition = aws_vpc.main.id != ""
error_message = "VPC should be created with valid ID"
}
assert {
condition = aws_vpc.main.state == "available"
error_message = "VPC should be in available state"
}
}Mock Tests (Plan Mode, No Credentials)
# tests/vpc_module_mock_test.tftest.hcl
mock_provider "aws" {
mock_resource "aws_instance" {
defaults = {
id = "i-1234567890abcdef0"
instance_type = "t2.micro"
ami = "ami-12345678"
public_ip = "203.0.113.1"
private_ip = "10.0.1.100"
}
}
mock_resource "aws_vpc" {
defaults = {
id = "vpc-12345678"
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
}
mock_resource "aws_subnet" {
defaults = {
id = "subnet-12345678"
vpc_id = "vpc-12345678"
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
map_public_ip_on_launch = false
}
}
mock_data "aws_ami" {
defaults = {
id = "ami-0c55b159cbfafe1f0"
name = "ubuntu-focal-20.04-amd64"
}
}
mock_data "aws_availability_zones" {
defaults = {
names = ["us-west-2a", "us-west-2b", "us-west-2c"]
}
}
}
run "test_instance_with_mocks" {
command = plan
variables {
instance_type = "t2.micro"
ami_id = "ami-12345678"
}
assert {
condition = aws_instance.example.instance_type == "t2.micro"
error_message = "Instance type should match input variable"
}
assert {
condition = aws_instance.example.id == "i-1234567890abcdef0"
error_message = "Mock should return consistent instance ID"
}
}
run "test_data_source_with_mocks" {
command = plan
assert {
condition = data.aws_ami.ubuntu.id == "ami-0c55b159cbfafe1f0"
error_message = "Mock data source should return predictable AMI ID"
}
assert {
condition = length(data.aws_availability_zones.available.names) == 3
error_message = "Should return 3 mocked availability zones"
}
assert {
condition = contains(data.aws_availability_zones.available.names, "us-west-2a")
error_message = "Should include us-west-2a in mocked zones"
}
}
run "test_outputs_with_mocks" {
command = plan
assert {
condition = output.vpc_id == "vpc-12345678"
error_message = "VPC ID output should match mocked value"
}
assert {
condition = can(regex("^vpc-", output.vpc_id))
error_message = "VPC ID output should have correct format"
}
}
run "test_conditional_resources_with_mocks" {
command = plan
variables {
create_bastion = true
create_nat_gateway = false
}
assert {
condition = length(aws_instance.bastion) == 1
error_message = "Bastion should be created when enabled"
}
assert {
condition = length(aws_nat_gateway.nat) == 0
error_message = "NAT gateway should not be created when disabled"
}
}
run "test_tag_inheritance_with_mocks" {
command = plan
variables {
common_tags = {
Environment = "test"
ManagedBy = "Terraform"
}
}
assert {
condition = alltrue([
for key in keys(var.common_tags) :
contains(keys(aws_instance.example.tags), key)
])
error_message = "All common tags should be present on instance"
}
}
run "test_invalid_cidr_with_mocks" {
command = plan
variables {
vpc_cidr = "invalid"
}
expect_failures = [
var.vpc_cidr
]
}
run "setup_vpc_with_mocks" {
command = plan
variables {
vpc_cidr = "10.0.0.0/16"
vpc_name = "test-vpc"
}
assert {
condition = aws_vpc.main.cidr_block == "10.0.0.0/16"
error_message = "VPC CIDR should match input"
}
}
run "test_subnet_references_vpc_with_mocks" {
command = plan
variables {
vpc_id = run.setup_vpc_with_mocks.vpc_id
subnet_cidr = "10.0.1.0/24"
}
assert {
condition = aws_subnet.example.vpc_id == run.setup_vpc_with_mocks.vpc_id
error_message = "Subnet should reference VPC from previous run"
}
}Mock Providers
Mock providers simulate provider behavior without creating real infrastructure (Terraform 1.7.0+). Use them for fast, credential-free unit tests.
Basic Mock Provider
mock_provider "aws" {
mock_resource "aws_instance" {
defaults = {
id = "i-1234567890abcdef0"
instance_type = "t2.micro"
ami = "ami-12345678"
public_ip = "203.0.113.1"
private_ip = "10.0.1.100"
}
}
mock_data "aws_ami" {
defaults = {
id = "ami-0c55b159cbfafe1f0"
}
}
mock_data "aws_availability_zones" {
defaults = {
names = ["us-west-2a", "us-west-2b", "us-west-2c"]
}
}
}
run "test_with_mocks" {
command = plan # Mocks only work with plan mode
assert {
condition = aws_instance.example.id == "i-1234567890abcdef0"
error_message = "Mock instance ID should match"
}
}Aliased Mock Provider
mock_provider "aws" {
alias = "mocked"
mock_resource "aws_s3_bucket" {
defaults = {
id = "test-bucket-12345"
arn = "arn:aws:s3:::test-bucket-12345"
}
}
}
run "test_with_aliased_mock" {
command = plan
providers = {
aws = provider.aws.mocked
}
assert {
condition = aws_s3_bucket.example.id == "test-bucket-12345"
error_message = "Bucket ID should match mock"
}
}Common Mock Defaults
mock_provider "aws" {
mock_resource "aws_instance" {
defaults = {
id = "i-1234567890abcdef0"
arn = "arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0"
instance_type = "t2.micro"
ami = "ami-12345678"
availability_zone = "us-west-2a"
subnet_id = "subnet-12345678"
vpc_security_group_ids = ["sg-12345678"]
associate_public_ip_address = true
public_ip = "203.0.113.1"
private_ip = "10.0.1.100"
tags = {}
}
}
mock_resource "aws_vpc" {
defaults = {
id = "vpc-12345678"
arn = "arn:aws:ec2:us-west-2:123456789012:vpc/vpc-12345678"
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
tags = {}
}
}
mock_resource "aws_subnet" {
defaults = {
id = "subnet-12345678"
arn = "arn:aws:ec2:us-west-2:123456789012:subnet/subnet-12345678"
vpc_id = "vpc-12345678"
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
map_public_ip_on_launch = false
tags = {}
}
}
mock_resource "aws_s3_bucket" {
defaults = {
id = "test-bucket-12345"
arn = "arn:aws:s3:::test-bucket-12345"
bucket = "test-bucket-12345"
bucket_domain_name = "test-bucket-12345.s3.amazonaws.com"
region = "us-west-2"
tags = {}
}
}
mock_data "aws_ami" {
defaults = {
id = "ami-0c55b159cbfafe1f0"
name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210430"
architecture = "x86_64"
root_device_type = "ebs"
virtualization_type = "hvm"
}
}
mock_data "aws_availability_zones" {
defaults = {
names = ["us-west-2a", "us-west-2b", "us-west-2c"]
zone_ids = ["usw2-az1", "usw2-az2", "usw2-az3"]
}
}
mock_data "aws_vpc" {
defaults = {
id = "vpc-12345678"
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
}
}When to Use Mocks
Good fit:
- Testing Terraform logic, conditionals,
for_each/countexpressions - Validating variable transformations and output calculations
- Local development without cloud credentials
- Fast CI/CD feedback loops
Not a good fit:
- Validating actual provider API behavior
- Testing real resource creation side effects
- End-to-end integration testing
Limitations
- Plan mode only — mocks don't work with
command = apply - Mock defaults may not reflect real computed attribute values
- Mocks need manual updates when provider schemas change
- Can't test real resource dependencies or timing
Related skills
Forks & variants (1)
Terraform Test has 1 known copy in the catalog totaling 35 installs. They canonicalize to this original listing.
- hashicorp - 35 installs
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.