Introduction
Organizations frequently use Terraform Modules to manage complex resource provisioning and to provide a straightforward interface for developers to input the necessary parameters for deploying desired infrastructures. Modules facilitate code reuse and allow organizations to standardize the deployment of common workloads, such as three-tier web applications, cloud networking environments, or data analytics pipelines. When developing Terraform modules, module authors typically begin with manual testing. This involves using commands such as `terraform validate` for syntax checking, `terraform plan` to preview the execution plan, and `terraform apply`, followed by a manual inspection of resource configurations in the AWS Management Console. However, manual testing is susceptible to human error, is not scalable, and can lead to unintended issues. Since modules are utilized by multiple teams within an organization, it is crucial to ensure that any changes to the modules undergo thorough testing before being released. In this blog post, we will demonstrate how to validate Terraform modules and automate this process using a Continuous Integration/Continuous Deployment (CI/CD) pipeline.
What will be covered by this post?
✅ Get started with Terraform Testing using the new native command
✅ Key Features of the Testing “Framework”
✅ Test Syntax: Building Blocks
✅ Best Practices for Writing IaC Tests with OpenTofu / Terraform
Disclaimer
- To make my writing more concise, I will refer to the new “Terraform/OpenTofu” test feature simply as the “Terraform / OpenTofu” test feature. I will only mention “Terraform only” or “OpenTofu only” explicitly when discussing capabilities that apply specifically to that technology and not the other.
- Commands
tofu
andterraform
are interchangeable in this post; using one or another should have no impact on the IaC testing goal.
The “new” Terraform / OpenTofu Test
Terraform, a widely adopted IaC tool, has traditionally relied on third-party solutions for testing. Enter OpenTofu, a native testing framework integrated within Terraform, aiming to streamline and simplify the testing process.
The new Terraform / OpenTofu testing framework represents an evolution of the testing capabilities provided by Terraform. Built to align with Infrastructure as Code (IaC) principles, it enables module authors to validate configurations without introducing risks to existing infrastructure. Unlike traditional approaches that relied heavily on tools like TerraTest, OpenTofu simplifies the process with built-in support for HCL-based tests.
What Terraform / OpenTofu Test
Terraform (OpenTofu) tests let authors validate that module configuration updates do not introduce breaking changes.
Tests run against test-specific, short-lived resources, preventing any risk to your existing infrastructure or state.
Key Features of the Terraform / OpenTofu Testing Framework
Seamless Integration and Discovery
- Test files are identified using the extensions
.tftest.hcl
or.tftest.json
. - HCL syntax makes it intuitive for users already familiar with Terraform modules.
Unified Testing Philosophy
- Unit Tests: Validate logical operations, input validations, and custom conditions using
plan
commands. - Integration Tests: Perform end-to-end testing by applying resources in isolated environments and validating outputs.
Declarative Syntax
- OpenTofu leverages Terraform’s declarative language, making tests familiar and easy to write for existing Terraform users.
Resource Mocking
- Simulate external dependencies like APIs or cloud resources during testing to isolate the behavior of your IaC code.
Assertions
- Verify the expected state of your infrastructure after applying configurations.
- OpenTofu supports various assertion types, including existence, attribute checks, and relationship validation.
Parallel Execution (Only OpenTofu as of the time of writing this article)
- Optimize testing time by running tests concurrently, especially beneficial for large infrastructure configurations.
Enhanced Developer Experience
- OpenTofu’s
tofu fmt
provides built-in formatting for test files. (only OpenTofu) - Faster initialization with
tofu init
, outperforming Terraform’s initialization process. (only OpenTofu) - Comprehensive, verbose output following Go’s test framework style.
OpenTofu Test Syntax: Building Blocks
- Run Blocks:
- Encapsulate specific test logic. Support optional parameters like
command
(plan
for unit tests,apply
for integration tests).
- Encapsulate specific test logic. Support optional parameters like
- Variable Blocks:
- Define reusable variables across multiple tests.
variables { region = "us-east-1" environment = "test" }
- Provider Blocks:
- Include necessary provider configurations for the tests.
- Assertions:
- Define conditions to validate outputs or behavior.
- Support custom error messages for failed validations.
- Expect Failures:
- Enable negative testing to ensure robust validation.
run "invalid_input" { variables = { instance_type = "invalid-type" } command = "plan" expect_failures = ["Invalid instance type"] }
Comparative Analysis: OpenTofu vs Terraform Test
Feature | OpenTofu Test | Terraform Test |
---|---|---|
Licensing | Open-source, stable | BSL-licensed, experimental |
Initialization | Faster (tofu init ) | Slower |
Test Execution Speed | Faster but CPU-intensive | Moderate |
Formatting Support | Available (tofu fmt ) | Lacking |
Signal Handling | Supports Unix signals (e.g., C-C ) | Not robust |
Output Style | Verbose, Go-test-like | Basic |
Best Practices for Writing OpenTofu Tests
- Granular Test Design:
- Separate unit and integration tests into distinct files (e.g.,
unit_tests.tftest.hcl
,integration_tests.tftest.hcl
). - Break down test scenarios to focus on single functionalities.
- Separate unit and integration tests into distinct files (e.g.,
- Validation Rules:
- Use validation blocks in
variables.tf
to define strict input requirements.
variable "settings" { type = any validation { condition = length(keys(var.settings)) > 0 error_message = "At least one setting must be provided." } }
- Use validation blocks in
- Negative Testing:
- Incorporate tests that intentionally fail to validate edge cases.
- Dependency Management:
- Use modular structures with
testing_setup
folders for dependencies in integration tests.
- Use modular structures with
- Reuse Outputs:
- Reference outputs from setup blocks in subsequent tests for efficient resource management.
Practical Example: Integration Testing a Security Group Module
Directory Structure:
module/
tests/
integration_tests.tftest.hcl
testing_setup/
setup_vpc.tf
Setup VPC:
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
}
output "vpc_id" {
value = aws_vpc.vpc.id
}
Integration Test:
run "setup_vpc" {
command = "apply"
module {
source = "../testing_setup"
}
assert {
condition = aws_vpc.vpc.id != null
error_message = "VPC ID is missing."
}
}
run "validate_security_group" {
command = "apply"
variables = {
vpc_id = run.setup_vpc.vpc_id
}
assert {
condition = aws_security_group.sg.vpc_id == var.vpc_id
error_message = "Security group not associated with the correct VPC."
}
}
Integrating OpenTofu Tests with CI/CD Using GitHub Actions
A robust CI/CD pipeline is critical for maintaining high-quality Infrastructure as Code. Below is a step-by-step guide to integrate OpenTofu testing into your GitHub Actions pipeline:
Example Workflow File: .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- "*"
jobs:
test:
name: Run OpenTofu Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up OpenTofu
run: |
curl -fsSL https://opentofu.org/install.sh | bash
export PATH=$HOME/.opentofu/bin:$PATH
- name: Initialize Terraform
run: |
tofu init
- name: Run Tests
run: |
tofu test
- name: Format and Validate
run: |
tofu fmt -check
tofu validate
- name: Archive Test Results
if: failure()
run: |
mkdir -p artifacts
mv test-results/* artifacts/
continue-on-error: true
- name: Upload Artifacts
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: artifacts/
Key Points:
- Tool Installation:
- Use the OpenTofu installation script for setup.
- Ensure the
PATH
is updated to include OpenTofu binaries.
- Testing Steps:
- Run
tofu init
to initialize modules. - Use
tofu test
to execute the test suite.
- Run
- Error Handling and Artifacts:
- Archive test results for post-mortem analysis on failures.
- Formatting and Validation:
- Leverage
tofu fmt -check
andtofu validate
for pre-merge checks.
- Leverage
Conclusion and Call to Action
OpenTofu / Terraform testing framework democratizes testing for IaC practitioners by integrating a robust, intuitive, and performant solution directly into their workflow. The integration of this framework into CI/CD pipelines ensures that every change to your IaC modules is rigorously validated before deployment.
Adopt a Test-Driven Development (TDD) approach for your IaC projects. Write tests first, validate against them, and iterate to ensure your modules are resilient, maintainable, and future-proof. Let OpenTofu be your trusted ally in this journey.