First time at Zeet?

20 Apr
2024
-
21
min read

Comprehensive Terraform Test Strategies to Enhance Your Deployment Success

Ensure the success of Terraform deployments with these proven Terraform test strategies designed to streamline workflow and increase efficiency.

Jack Dwyer

Product
How To
Content
heading2
heading3
heading4
heading5
heading6
heading7

Share this article

Testing in infrastructure as code using Terraform is crucial for ensuring that changes to infrastructure are correct, safe, and up to expected standards before applying them to live environments. This process helps to prevent costly mistakes and disruptions, resulting in more reliable, stable, and secure infrastructure. By running tests, we can verify that the infrastructure changes made through Terraform are working as intended, without any unintended consequences. Understanding what is Terraform and how it operates is essential for effectively managing infrastructure as code.

Different Types of Terraform Tests

Different Terraform Test running on the computer

1. Unit Tests

Unit tests focus on specific components or modules of infrastructure configurations. These tests validate individual units of related code, ensuring they work correctly. In the context of Terraform, unit tests can help verify that modules are functioning as expected and that any changes do not introduce breaking changes.

2. Integration Tests

Integration tests check how different modules, components, or infrastructure configurations interact with each other. These tests help ensure that all parts of the infrastructure work well together and that the changes in one component do not negatively impact others.

3. End-to-End Tests

End-to-end tests validate the entire infrastructure setup as a whole. These tests check if the infrastructure behaves as expected from the viewpoint of the end-user or the application it supports. Running end-to-end tests is essential to confirm that the overall infrastructure meets the desired functionality and requirements.

Using Terraform Test Framework

The Terraform test framework, introduced in Terraform 1.6, provides an automated way to conduct unit and integration tests for Terraform configurations and modules. This framework uses the HCL syntax, familiar to Terraform users, making it easy to write tests for infrastructure changes. Authors can run these tests against temporary resources, mitigating risks to existing infrastructure or state.

Integration or Unit Testing in Terraform

Terraform tests create real infrastructure by default, allowing users to validate Terraform's core functions and operations. This method is similar to integration testing, where infrastructure is created and validated. 

Users can override the default behavior and perform unit testing by specifying that Terraform should not create new infrastructure during testing. This allows for testing logical operations and custom conditions without altering the infrastructure. Terraform v1.7.0 introduced the ability to simulate provider data during test execution, aiding in writing more detailed unit tests.

Syntax and Execution of Terraform Tests

Each Terraform test file must have a .tftest.hcl or .tftest.json extension, and it typically contains run blocks, variables blocks, and provider blocks. The run blocks are executed in order, simulating Terraform commands in the configuration directory. Variables and provider blocks are processed at the beginning of the test operation, and the order of these blocks does not matter. By organizing the variables and providers first, users can streamline the testing process and create more efficient tests.

Related Reading

Example of a Terraform Test File

Here's an example of a Terraform test file that verifies the creation of an AWS S3 bucket with a specific name:

	
hcl
# main.tf

provider "aws" {
	region = "eu-central-1"
}

variable "bucket_prefix" {
 type = string
}

resource "aws_s3_bucket" "bucket" {
 bucket = "${var.bucket_prefix}-bucket"
}

output "bucket_name" {
 value = aws_s3_bucket.bucket.bucket
}
	


In the test file `valid_string_concat.tftest.hcl`, we verify that the bucket name was created as expected:

	
hcl
# valid_string_concat.tftest.hcl

variables {
 bucket_prefix = "test"
}
 
run "valid_string_concat" {
  
 command = plan
  
 assert {
  condition     = aws_s3_bucket.bucket.bucket == "test-bucket"
  error_message = "S3 bucket name did not match expected"
 }
  
}
	


In the example above, the test file uses input variables to define the bucket prefix and then checks if the bucket name is correctly created as expected. The run block specifies the command to execute, `plan`, and then defines an assertion to verify if the bucket name matches the expected value. If the condition fails, an error message is displayed.

This is how you can write a Terraform test file to validate the creation of AWS S3 buckets.

Zeet Terraform and Helm Product Overview

Understanding and Utilizing the Terraform Test Command

Laptop placed on the table while running Terraform Test

The Terraform test command is a powerful tool specifically designed to run tests written in Terraform configurations. It reads the test files defined in your configuration files with .tftest.json and .tftest.hcl extensions and executes the tests defined in those files. By default, Terraform will search for these files in the current directory and the specified testing directory (which is typically named 'tests').

Setting Up a Basic Terraform Configuration File for Testing

To set up a basic Terraform configuration file for testing, you can create a file with .tftest.hcl extension. You can define your test blocks that contain the test information. Each test block contains the provider, resource, and configuration block for the test. For example, you can set up a test to verify the creation of a specific AWS S3 bucket.

Executing the Terraform Test Command

To execute the Terraform test command, you need to navigate to the directory where your Terraform configurations are stored and run the command in the terminal. You can also run the terraform test command with optional attributes, such as -test-directory=<directory>, -json, -verbose, or -filter=testfile, to customize your testing operation. For instance, if you want to test only specific files, you can use the -filter attribute.

Zeet for Seamless Cloud Deployments

Zeet helps you to get more from your cloud, Kubernetes, and Terraform investments and helps your engineering team become strong individual contributors through our CI/CD & deployment platform. 

Contact Zeet to learn more about how Zeet helps you get seamless cloud deployments every time, and helps your team to become a top-performing engineering team.

How to Perform Effective Unit and Contract Testing in Terraform

Person using dual monitor setup while running Terraform Test

Unit Testing in Terraform

Unit testing is a vital aspect of Terraform development. These tests focus on testing each component of the Terraform code individually to ensure they function correctly. This approach allows us to validate each module's logic independently. For example, let's say we have a Terraform module that deploys an AWS S3 bucket. We can write a unit test to confirm that this specific configuration deploys the bucket correctly.

Let me walk you through the process of writing a simple unit test for a Terraform module that creates an AWS S3 bucket. Here's the Python code using AWS CDK for Terraform:

	
python
import unittest
from aws_cdk import App, Stack
from my_constructs import S3Bucket

class TestS3Bucket(unittest.TestCase):
	 def test_creates_bucket_with_correct_configuration(self):
   	 app = App()
     stack = Stack(app, 'test-stack', env={'region': 'eu-west-2'})
     
     # Create the construct with specific configurations
     bucket_name = 'my-test-bucket'
     lifecycle_rules = [
     	  {'expiration': {'days': 90}, 'id': 'log-files', 'prefix': 'logs/'},
        {'expiration': {'days': 120}, 'id': 'backup-files', 'prefix': 'backup/'},
     ]
     access_control_policy = {
       'actions': ['s3:GetObject'],
       'effect': 'Allow',
       'principals': [{'service': 'lambda.amazonaws.com'}],
       'resources': [f'arn:aws:s3:::{bucket_name}/backup/*'],
     }
     S3Bucket(stack, 's3-bucket', bucket_name=bucket_name, lifecycle_rules=lifecycle_rules, access_control_policy=access_control_policy)
     
     # Deploy the stack and get the template
     synthed = app.synth()
     template = synthed.get_stack(stack.stack_name).template
     
     # Validate the bucket was deployed with the correct configuration
     self.assertEqual(template['Resources'][bucket_name]['Type'], 'AWS::S3::Bucket')
     # Add more assertions for other properties as needed...
     
if __name__ == '__main__':
	  unittest.main()
	

Contract Testing in Terraform

Contract testing ensures that the interaction between different modules in Terraform respects the expected contract. These tests confirm that a configuration using a Terraform module adheres to the correct input format, thus validating the module's intended use. Let's delve into a contract testing example that enforces specific rules for an EC2 instance type:

	
hcl
variable "instance_type" {
 type        = string
 description = "The instance type for the EC2 instance."
 
 validation {
  condition     = contains(["t2.micro", "t2.small", "t3.micro"], var.instance_type)
  error_message = "The instance type must be t2.micro, t2.small, or t3.micro."
 }
}

resource "aws_instance" "example" {
 instance_type = var.instance_type
}
	

In this code snippet, we validate the `instance_type` variable to only allow three specific values. This way, contract testing ensures that users adhere to the intended use of the Terraform module.

Related Reading

Integrating Comprehensive Integration Testing in Terraform Workflows

Glasses placed on the computer that is running Terraform Test

Integration testing involves testing your entire infrastructure configuration, including any dependencies on other resources, to ensure that they work together correctly. Terraform has built-in dependency mapping and management that can be utilized to make sure the changes being made are as expected.

You can use tools like Terratest or Kitchen-Terraform for integration testing. The following tools can also be used in the workflow, and including them in the CI pipeline will form Integration Testing:

Terraform fmt

To format the code correctly.

Terraform validate

To verify the syntax.

Terraform plan

To verify the config file will work as expected.

TFLint

To verify the contents of the configuration as well as the syntax and structure, also checks account limits (e.g. that a VM instance type is valid, and that the limit on the number of VMs in Azure has not been reached).

Kitchen-Terraform

Kitchen-Terraform is an open-source tool that provides a framework for writing automated tests that validate the configuration and behavior of Terraform code, including testing for errors, resource creation, destruction, and validation of outputs. Kitchen-Terraform uses a combination of Ruby, Terraform, and Test Kitchen to spin up infrastructure in a sandbox environment, run tests, and destroy the infrastructure once testing is complete.

Terratest

Terratest is an open-source testing framework for testing Terraform that can also be used to test Kubernetes, Docker, and Packer, amongst others. Terratest enables automated testing of infrastructure code in a sandbox environment to validate that it meets requirements, functions as expected, and is reliable. 

Tests are written in Go, and it provides a rich set of testing functions that allow the user to automate the entire testing process, including provisioning resources, running tests, and cleaning up resources. Terratest also provides built-in support for popular testing frameworks like Ginkgo and Gomega.

How to use Terratest in Terraform Testing

In the Terratest example below, a function is created to test the creation of an Azure storage account in a resource group, and a container in the storage account, then verifies that the storage account and container exist. The resources are then cleaned up after the test is complete.

package test

import (
"context"
"fmt"
"testing"
"time"

"github.com/Azure/azure-sdk-for-go/storage"
"github.com/gruntwork-io/terratest/modules/azure"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/testing"
)

func TestAzureStorageAccount(t *testing.T) {
t.Parallel()

Set the Azure subscription ID and resource group where the Storage Account will be created

 uniqueID := random.UniqueId()
subscriptionID := "YOUR_AZURE_SUBSCRIPTION_ID"
resourceGroupName := fmt.Sprintf("test-rg-%s", uniqueID)

Create the resource group

 azure.CreateResourceGroup(t, resourceGroupName, "uksouth")

Set the Storage Account name and create the account

 accountName := fmt.Sprintf("teststorage%s", uniqueID)
accountType := storage.StandardZRS
location := "uksouth"
account := azure.CreateStorageAccount(t, subscriptionID, resourceGroupName, accountName, accountType, location)

Create a container in the Storage Account

 ctx := context.Background()
client, err := storage.NewBasicClient(accountName, azure.GenerateAccountKey(t, accountName))
if err != nil {
  t.Fatalf("Failed to create Storage Account client: %v", err)
}
service := client.GetBlobService()
containerName := fmt.Sprintf("testcontainer%s", uniqueID)
if _, err := service.CreateContainer(ctx, containerName, nil); err != nil {
  t.Fatalf("Failed to create container: %v", err)
}

Check if the container exists

 testing.WaitForTerraform(ctx, terraformOptions, nil)

Cleanup resources

 defer azure.DeleteResourceGroup(t, resourceGroupName)
}

End-to-End Testing Strategies for Terraform Deployments

Terraform Test displayed on the monitor

End-to-end testing involves testing the complete functionality of an infrastructure deployment from start to finish. This can be done in a staging environment, and tools like Terratest can be utilized for automated end-to-end testing of infrastructure managed by Terraform. 

In the context of Azure storage accounts, end-to-end testing can entail validating the existence of a created Azure Storage account and the correctness of the container's name and access type.

Here's how to implement end-to-end testing with Terraform using Terratest:

1. Create Terraform Configuration

Define an Azure Storage account and container in a Terraform configuration file.

2. Implement Terratest Test

Develop a Terratest test file to deploy the Terraform configuration and verify the created Azure Storage account and container. This test file will access the Azure Storage account and container information using the Azure SDK.

3. Run the Test

Execute the test by navigating to the directory where the test file is saved and running the command 'go test -v'.

The test validates that the Azure Storage account and container were created correctly and have the expected configurations. This end-to-end testing ensures that the infrastructure deployment functions as intended.

Mock Terraform Tests and Best Practices for Efficient Testing

Girl giving up victory igns while sitting beside the computer running Terraform Test

Mock testing in Terraform projects is a powerful tool for ensuring the quality and correctness of your infrastructure code without the need to create actual resources or require real credentials. Test mocking, available in Terraform v1.7.0 and later, enables the creation of dummy versions of providers, resources, and data sources for testing purposes. 

By using mock providers, you can simulate the behavior of actual cloud providers, allowing you to test your configurations thoroughly. These mocked resources can be defined in your test files, and they generate fake data for computed attributes. By leveraging test mocking, you can streamline your testing process and catch potential issues early on in your development cycle. 

Overrides for Testing in Terraform

Overrides in Terraform are a valuable feature for modifying existing configurations for testing purposes. Overrides enable developers to alter resource attributes, data source attributes, or even provider settings without changing the actual infrastructure. 

By using override blocks with the target attribute, you can specify what resources, data sources, or modules you want to modify for your tests. Overrides are particularly useful for testing configurations with complex structures, ensuring that your code behaves as expected even when actual resources are not being manipulated.

Linting Tools for Terraform Projects

Linting tools play a crucial role in maintaining code quality and consistency in Terraform projects. By using tools like Terraform fmt, TFLint, Checkov, and Terrascan, you can identify syntax errors, enforce style guidelines, and detect potential issues before applying any changes to your infrastructure. 

Terraform fmt is particularly useful for formatting Terraform code based on standard rules, while TFLint checks for syntax errors, best practices, and code style consistency. Checkov focuses on security and compliance issues in Terraform code, offering suggestions for fixing vulnerabilities. Terrascan performs static code analysis to identify security vulnerabilities and compliance violations. By incorporating linting tools into your development workflow, you can improve code quality, enhance collaboration, and automate code review processes.

Compliance Testing with Terraform-Compliance

Terraform-compliance is a powerful tool for ensuring compliance with specific conditions in your Terraform code. By writing conditions in YAML files and testing your code against them, you can verify that your infrastructure configurations comply with your defined rules. 

For instance, you can specify conditions such as ensuring that an Azure Storage Account is not publicly accessible. By running terraform-compliance with the appropriate policy files, you can validate your code against these conditions and prevent non-compliant configurations from being deployed.

Drift Testing in Terraform

Drift testing in Terraform is essential for detecting differences between your code and the real infrastructure. When running `terraform plan`, Terraform compares the current state of your infrastructure to the state saved in the state file, highlighting any divergences. 

You can use driftctl, a free open-source tool, to report on infrastructure drift and identify resources that are not managed by Terraform or are missing on the cloud provider. By performing drift testing, you can ensure that your infrastructure remains in sync with your configuration and address any discrepancies promptly.

Zeet Contact Us

Get Control of Your Releases With Zeet's CI/CD & Deployment Platform for Kubernetes and Terraform

Zeet helps you to get more from your cloud, Kubernetes, and Terraform investments and helps your engineering team become strong individual contributors through our CI/CD & deployment platform. 

Contact Zeet to learn more about how Zeet help you get seamless cloud deployments every time, and helps your team to become a top-performing engineering team.

Related Reading

Subscribe to Changelog newsletter

Jack from the Zeet team shares DevOps & SRE learnings, top articles, and new Zeet features in a twice-a-month newsletter.

Thank you!

Your submission has been processed
Oops! Something went wrong while submitting the form.