
Terraform Test
Add Terraform fmt, validate, and terraform test unit/integration jobs to GitHub Actions or GitLab CI for IaC you ship with confidence.
Install
npx skills add https://github.com/hashicorp/agent-skills --skill terraform-testWhat is this skill?
- GitHub Actions pipeline: fmt check, init, validate, then terraform test -filter=unit_test
- Integration job gated on main with AWS secrets for apply-mode tests
- GitLab CI validate and test stages with hashicorp/terraform container image
- Separation of unit tests (plan mode, no cloud creds) vs integration tests
- Terraform 1.9.x version pinning via setup-terraform and image tags
Adoption & trust: 3.5k installs on skills.sh; 654 GitHub stars; 3/3 security scanners passed (skills.sh audits); trending (+100% hot-view momentum).
Recommended Skills
Journey fit
Automated Terraform tests gate merges and main-branch deploys, which is core Ship testing discipline even though modules are authored in Build. terraform test in plan and apply modes is explicitly a testing subphase concern with CI wiring, filters, and credential boundaries.
Common Questions / FAQ
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.
SKILL.md
READMESKILL.md - Terraform Test
# CI/CD Integration ## GitHub Actions ```yaml 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 ```yaml 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: - main ``` ## Recommended 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_test` to 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) ```hcl # 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_cid