| name | terraform-tests |
| description | Generate tests for Terraform modules. Use everytime tests are missing or need to be updated. |
| metadata | {"author":"pagopa-dx","version":"1.1"} |
| compatibility | requires terraform, go, and access to the internet |
Terraform Module Testing Skill
This skill generates comprehensive test suites for Terraform modules following HashiCorp's Terraform Testing Framework best practices. Tests are organized into four layers: Unit, Contract, Integration, and End-to-End (E2E).
For AI Agents: This skill references detailed documentation files. All relative paths (e.g., reference/unit-tests.md) are relative to .github/skills/terraform-tests/ from the workspace root. When you need detailed information about a specific test layer, use read_file to load the referenced documentation file.
Test Philosophy
Based on HashiCorp's Testing Framework:
- Unit Tests: Fast, mocked tests that verify module logic without provisioning real infrastructure
- Contract Tests: Validate input contracts, constraints, and expected failures
- Integration Tests: Provision real infrastructure to test module behavior in isolation
- E2E Tests: Deploy complete scenarios with workloads to verify end-to-end functionality
Test Structure
tests/
โโโ unit.tftest.hcl
โโโ contract.tftest.hcl
โโโ integration.tftest.hcl
โโโ e2e_test.go
โโโ go.mod
โโโ README.md
โโโ setup/
โ โโโ main.tf
โ โโโ variables.tf
โ โโโ outputs.tf
โ โโโ providers.tf
โ โโโ README.md
โโโ apps/
โโโ <scenario_name>/
โโโ Dockerfile
โโโ project.json
โโโ README.md
โโโ src/
โโโ go.mod
โโโ main.go
Test Execution
Tests are run using NX commands:
nx run <module-name>:test:unit
nx run <module-name>:test:contract
nx run <module-name>:test:integration
nx run <module-name>:test:e2e
Important: After modifying unit and contract tests, always execute them to verify they pass. Integration and E2E tests are slow and should not be run during development, only in CI/CD.
Test Layers Quick Reference
1. Unit Tests
- Purpose: Verify module logic without provisioning resources
- Details: See reference/unit-tests.md
- Key: Use mocked providers, test logic and computations
2. Contract Tests
- Purpose: Validate input constraints and expected failures
- Details: See reference/contract-tests.md
- Key: Use
expect_failures, test validation rules
3. Integration Tests
- Purpose: Provision real infrastructure to test module behavior
- Details: See reference/integration-tests.md
- Key: Use real providers, reference
tests/setup/ module, use "int" in domain names
4. Setup Module
- Purpose: Shared infrastructure for integration tests only
- Details: See reference/setup-module.md
- Key: Reuse infrastructure from
infra/resources/_modules/testing
5. E2E Tests
- Purpose: Deploy complete scenarios with workloads
- Details: See reference/e2e-tests.md
- Key: Deploy from
examples/, call test app APIs, verify behavior
6. Test Applications
- Purpose: Containerized apps that expose HTTP APIs for E2E verification
- Details: See reference/test-applications.md
- Key: Simple Go HTTP servers, use DefaultAzureCredential
7. Examples Structure
- Purpose: Structure for E2E test fixtures and module under test
- Details: See reference/examples-structure.md
- Key:
fixtures.tf (infrastructure) + mut.tf (module under test)
Usage Scenarios
Scenario 1: New Module
When creating a new Terraform module:
- Analyze the module: Understand variables, resources, logic, and use cases
- Generate unit.tftest.hcl: Mock providers, test logic and defaults
- Generate contract.tftest.hcl: Test validation rules and constraints
- Generate integration.tftest.hcl: Test real resource creation
- Create tests/setup/: Query existing infrastructure, output needed values
- Generate e2e_test.go: Deploy from examples/, call test app APIs
- Create examples/: Add
fixtures.tf and mut.tf for E2E tests
- Create test apps: Go HTTP servers exposing APIs for verification
- Generate tests/README.md: Use templates/test-readme.md
- Finalize: Run unit/contract tests, update mut.tf to registry+1, run
pnpm nx release plan
Scenario 2: Modify Existing Module
When modifying a module with modern tests:
- Analyze the change: Determine which test layers need updates
- Update unit tests: Add run blocks for new scenarios, execute tests
- Update contract tests: Add validation tests for new variables, execute tests
- Update integration tests: Add scenarios for new configurations if needed
- Update E2E tests: Add test functions for new scenarios if needed
- Update test apps: Add/modify endpoints to match new functionality
- Version bump: Run
pnpm nx release plan, update mut.tf if needed
Scenario 3: Upgrade Legacy Tests
When a module has legacy tests (single file, no modern structure):
- Analyze existing tests: Identify covered scenarios
- Delete legacy files completely: Remove old test files entirely
- Generate modern test suite: Follow "New Module" scenario
- Create missing components: Add setup/, examples/, apps/, e2e_test.go
- Verify coverage: Execute unit/contract tests, ensure no regression
- Version bump: Update mut.tf to registry+1, run
pnpm nx release plan
Best Practices
- Variable Reuse: Define variables once at the top, override in run blocks
- Descriptive Names: Use clear, descriptive run block names
- One Concern Per Test: Each run block tests one specific aspect
- Setup Module: Keep it lean, reuse existing infrastructure from
infra/resources/_modules/testing
- Real Providers in Integration: Never mock in integration tests
- Examples for E2E: E2E tests deploy from examples/, not module directly or tests/setup
- Deterministic Mocks: Use fixed IDs in override_data for consistency
- Comprehensive Assertions: Test all critical properties
- Error Messages: Provide helpful error_message in every assert
- Test Apps: Keep simple, expose APIs, ensure contract compatibility with E2E tests
- Cleanup: Always include teardown in E2E tests
- Documentation: Keep README.md updated with scenarios
- Naming Limits: Use "int" not "integration" in domain names for Azure length limits
- Random Instance Numbers: Use for Key Vault and other soft-delete resources
- Separate Infrastructures: Integration and E2E use separate resource groups and networks
- Execute Tests: Always run unit/contract tests after changes; skip integration/e2e during dev
Requirements Summary
- Module README: Do not create (only create tests/README.md)
- Mocking: Use mock_provider for unit and contract tests
- Setup folder: ONLY for integration tests (E2E uses examples/fixtures.tf)
- Test execution:
nx run <module-name>:test:<layer>
- Follow examples: azure_app_configuration and azure_cosmos_account modules
- Test naming:
module_name_feature_being_tested pattern
- Domain naming: Use "int" (not "integration") and "e2e" for Azure length limits
- Module reference: Local
source = "../.." during development, registry+version before commit
- Version bump: Run
pnpm nx release plan for patch bump when adding/modifying tests
- Execute tests: Always run unit/contract after changes; skip slow integration/e2e
- Legacy cleanup: Delete old test files completely when upgrading
- Examples structure: Must have fixtures.tf (infrastructure) + mut.tf (module)
- Random instances: Use random_integer for Key Vault and soft-delete resources
- API contract: Test apps must expose APIs compatible with E2E tests
Key Differences: Integration vs E2E
| Aspect | Integration Tests | E2E Tests |
|---|
| Infrastructure | Uses tests/setup/ module | Uses examples/fixtures.tf |
| Module source | Direct module reference | Module in examples/mut.tf |
| Test apps | Not used | Required (in tests/apps/) |
| Domain name | "int" | "e2e" |
| Separation | Separate from E2E infrastructure | Separate from integration infrastructure |
| Purpose | Test module in isolation | Test complete scenarios with workloads |
Notes
- Unit and contract tests run in CI on every PR
- Integration and E2E tests run on schedule (weekly/nightly)
- Integration and E2E tests require real Azure subscription and incur costs
- Test applications use DefaultAzureCredential (managed identity in tests)
- Setup module (tests/setup/) outputs drive integration test inputs only
- E2E tests use fixtures.tf in examples/, not tests/setup/
- Examples must be deployable independently for E2E tests
- Persistent test infrastructure (VNet, DNS zones, peering, Log Analytics, etc.) is defined in
infra/resources/_modules/testing
- Integration and E2E infrastructures are completely separate
- Always execute unit and contract tests after modifications to verify they pass
- Do not run integration/e2e tests during development (slow, expensive)
- Do not add comments that merely restate the obvious or act as decorative/redundant headers (for example,
this file contains unit tests or 3. Development use case when the run name already conveys that); comments are fine when they add necessary context such as non-obvious intent, workarounds, assumptions, or edge cases
Reference Documentation