Terraform Test By Terraform — A Native Testing Support for Infrastructure Provisioning
What is Terraform test?
Terraform Test By Terraform — A Native Testing Support for Infrastructure Provisioning
What is Terraform test?
Terraform test is native to Infrastructure As Code (IAC) tool terraform and that lets you validate your infrastructure code without impacting your existing state file or resources. As hashicorp says
Testing is a separate operation that is not part of a plan or apply workflow, but instead builds ephemeral infrastructure and tests your assertions against in-memory state for those short-lived resources. This lets you safely verify changes to your module without affecting your infrastructure.

source: https://www.hashicorp.com/blog/testing-hashicorp-terraform
How to create Terraform test files?
Terraform test files have the extension .tftest.hcl
Where to place my test files in Terraform project?
Terraform
- tests
- main.tftest.hcl
- variables.tf
- terraform.tfvars
- main.tf
- variables.tf
- terraform.tfvars
As you see, under the project “Terraform”, we have test folder where the test file is kept. The test files are recognized by extension .tftest.hcl. Here we have main.tftest.hcl under tests directory of the project.
By default when you run the terraform test command, Terraform looks for .tftest.hcl files in both the root directory and in the tests directory. You can tell Terraform to look in a different directory with the -test-directory flag. In this example, the tests directory contains all of the tests for the module
Example of terraform tests with Azure resources
Below is code snippet from the main.tf — where we are provisioning a resource group example and within that, we are further provisioning storage account using random string.
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.105.0"
}
}
}
provider "azurerm" {
subscription_id = "Provide the subscription id"
tenant_id = "Provide the tenant id"
client_id = "Provide the client id"
client_secret = "Provide the client secret"
features {
}
}
resource "random_string" "random" {
length = 16
special = false
}
resource "azurerm_resource_group" "example" {
name = "exampleresourcegroup"
location = "West Europe"
}
resource "azurerm_storage_account" "examplestorage" {
name = lower(random_string.random.id)
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "LRS"
access_tier = "Hot"
tags = {
environment = "staging"
}
}
resource "azurerm_storage_container" "data" {
name = "test"
storage_account_name = azurerm_storage_account.examplestorage.name
container_access_type = "blob"
}
resource "azurerm_storage_blob" "examplefilestorageblob" {
name = "testfile1.txt"
storage_account_name = azurerm_storage_account.examplestorage.name
storage_container_name = azurerm_storage_container.data.name
type = "Block"
source = "./fileToUpload/testfile1.txt"
}
Example test cases you want to verify
verify_resource_group:
- verify resource group name and location
verify_storage_account:
- verify if storage account name has all lowercase for its name
- verify if account storage type is not RAGRS
- verify if your storage account has hot access tier
verify_storage_account_container:
- verify storage account container name
- verify storage account container access type
- verify storage account name where the container resides
verify_uploaded_files:
- verify uploaded file name
- verify belonging storage account container name
Every test file is composed of root-level attributes and blocks as follows:
- One or more run blocks
- One or zero variables block
- Zero to many provider blocks.
Terraform executes test cases (run block commands) sequentially.
Below is the code snippet from main.tftest.hcl
# main.tftest.hcl
run "verify_resource_group" {
command = plan
assert {
condition = azurerm_resource_group.example.name == "exampleresourcegroup"
error_message = "Resource group name does not match expected value"
}
assert {
condition = azurerm_resource_group.example.location == "westeurope"
error_message = "Resource group location does not match expected value"
}
}
run "verify_storage_account" {
command = apply
assert {
condition = lower(azurerm_storage_account.examplestorage.name) == azurerm_storage_account.examplestorage.name
error_message = "Storage account name did not match expected"
}
assert {
condition = azurerm_storage_account.examplestorage.account_replication_type != "RAGRS"
error_message = "Storage account redundancy should not be Read-Access geo redundant"
}
assert {
condition = azurerm_storage_account.examplestorage.access_tier == "Hot"
error_message = "Default access tier should be Hot"
}
}
run "verify_storage_account_container" {
// Define any variables required for your test
variables {
storage_account_container_name = var.storage_account_container_name
}
command = apply
assert {
condition = azurerm_storage_container.data.name == var.storage_account_container_name
error_message = "Storage account container name should be test."
}
assert {
condition = azurerm_storage_container.data.container_access_type == "blob"
error_message = "Storage account container name should be test."
}
assert {
condition = azurerm_storage_container.data.storage_account_name == azurerm_storage_account.examplestorage.name
error_message = "Storage account container test should reside within storageaccountfortest."
}
}
run "verify_uploaded_files" {
// Define any variables required for your test
variables {
uploaded_file = var.uploaded_file
}
command = apply
assert {
condition = azurerm_storage_blob.examplefilestorageblob.name == var.uploaded_file
error_message = "Uploaded file name should be testfile1.txt."
}
assert {
condition = azurerm_storage_blob.examplefilestorageblob.storage_container_name == azurerm_storage_container.data.name
error_message = "Storage account container name should be test."
}
}
If you declare and define variables as per standard .tf and .tfvars file under test directory, the variables will be auto-evaluated
variables.tf
variable "storage_account_container_name" {
description = "This is a variable of type string which contains name of the storage account container"
type = string
}
variable "uploaded_file" {
description = "This is a variable of type string which contains name of the uploaded file in the storage container"
type = string
}
terraform.tfvars
storage_account_container_name = "test"
uploaded_file="testfile1.txt"
What is run block?
Using run block, you can define test name and put your assert command inside the run block.
What does “command=plan” within run block mean?
By default within run block, terraform test assumes command value is apply.
command=apply
It means while you will execute run block for test, terraform will create infrastructure in your intended platform, will execute test and then will destroy the infrastructure after testing is done. If it is unable to destroy any infrastructure , it will tell you that information and you need to manually delete the remained infrastructure. Replacing the command value with command=plan instructs Terraform to not create new infrastructure for this run block.
Output of above executed test cases
madhu@DESKTOP-JJQ0B6U MINGW64 ~/Terraform
$ terraform test
tests\main.tftest.hcl... in progress
run "verify_resource_group"... pass
run "verify_storage_account"... pass
run "verify_storage_account_container"... pass
run "verify_uploaded_files"... pass
tests\main.tftest.hcl... tearing down
tests\main.tftest.hcl... pass
Success! 4 passed, 0 failed.
Make storage account access tier as “Cool” and re-execute the test cases
madhu@DESKTOP-JJQ0B6U MINGW64 ~/Terraform
$ terraform test
tests\main.tftest.hcl... in progress
run "verify_resource_group"... pass
run "verify_storage_account"... fail
╷
│ Error: Test assertion failed
│
│ on tests\main.tftest.hcl line 33, in run "verify_storage_account":
│ 33: condition = azurerm_storage_account.examplestorage.access_tier == "Hot"
│ ├────────────────
│ │ azurerm_storage_account.examplestorage.access_tier is "Cool"
│
│ Default access tier should be Hot
╵
run "verify_storage_account_container"... pass
run "verify_uploaded_files"... pass
tests\main.tftest.hcl... tearing down
tests\main.tftest.hcl... fail
Failure! 3 passed, 1 failed.
One Test case fails but still continues to execute other test cases.
Use Terraform test with verbose option
madhu@DESKTOP-JJQ0B6U MINGW64 ~/Terraform
$ terraform test -verbose
tests\main.tftest.hcl... in progress
run "verify_resource_group"... pass
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
+ create
Terraform will perform the following actions:
# azurerm_resource_group.example will be created
+ resource "azurerm_resource_group" "example" {
+ id = (known after apply)
+ location = "westeurope"
+ name = "exampleresourcegroup"
}
# azurerm_storage_account.examplestorage will be created
+ resource "azurerm_storage_account" "examplestorage" {
+ access_tier = "Cool"
+ account_kind = "StorageV2"
+ account_replication_type = "LRS"
+ account_tier = "Standard"
+ allow_nested_items_to_be_public = true
+ cross_tenant_replication_enabled = true
+ default_to_oauth_authentication = false
+ dns_endpoint_type = "Standard"
+ enable_https_traffic_only = true
+ id = (known after apply)
+ infrastructure_encryption_enabled = false
+ is_hns_enabled = false
+ large_file_share_enabled = (known after apply)
+ local_user_enabled = true
+ location = "westeurope"
+ min_tls_version = "TLS1_2"
+ name = (known after apply)
+ nfsv3_enabled = false
+ primary_access_key = (sensitive value)
+ primary_blob_connection_string = (sensitive value)
+ primary_blob_endpoint = (known after apply)
+ primary_blob_host = (known after apply)
+ primary_blob_internet_endpoint = (known after apply)
+ primary_blob_internet_host = (known after apply)
+ primary_blob_microsoft_endpoint = (known after apply)
+ primary_blob_microsoft_host = (known after apply)
+ primary_connection_string = (sensitive value)
+ primary_dfs_endpoint = (known after apply)
+ primary_dfs_host = (known after apply)
+ primary_dfs_internet_endpoint = (known after apply)
+ primary_dfs_internet_host = (known after apply)
+ primary_dfs_microsoft_endpoint = (known after apply)
+ primary_dfs_microsoft_host = (known after apply)
+ primary_file_endpoint = (known after apply)
+ primary_file_host = (known after apply)
+ primary_file_internet_endpoint = (known after apply)
+ primary_file_internet_host = (known after apply)
+ primary_file_microsoft_endpoint = (known after apply)
+ primary_file_microsoft_host = (known after apply)
+ primary_location = (known after apply)
+ primary_queue_endpoint = (known after apply)
+ primary_queue_host = (known after apply)
+ primary_queue_microsoft_endpoint = (known after apply)
+ primary_queue_microsoft_host = (known after apply)
+ primary_table_endpoint = (known after apply)
+ primary_table_host = (known after apply)
+ primary_table_microsoft_endpoint = (known after apply)
+ primary_table_microsoft_host = (known after apply)
+ primary_web_endpoint = (known after apply)
+ primary_web_host = (known after apply)
+ primary_web_internet_endpoint = (known after apply)
+ primary_web_internet_host = (known after apply)
+ primary_web_microsoft_endpoint = (known after apply)
+ primary_web_microsoft_host = (known after apply)
+ public_network_access_enabled = true
+ queue_encryption_key_type = "Service"
+ resource_group_name = "exampleresourcegroup"
+ secondary_access_key = (sensitive value)
+ secondary_blob_connection_string = (sensitive value)
+ secondary_blob_endpoint = (known after apply)
+ secondary_blob_host = (known after apply)
+ secondary_blob_internet_endpoint = (known after apply)
+ secondary_blob_internet_host = (known after apply)
+ secondary_blob_microsoft_endpoint = (known after apply)
+ secondary_blob_microsoft_host = (known after apply)
+ secondary_connection_string = (sensitive value)
+ secondary_dfs_endpoint = (known after apply)
+ secondary_dfs_host = (known after apply)
+ secondary_dfs_internet_endpoint = (known after apply)
+ secondary_dfs_internet_host = (known after apply)
+ secondary_dfs_microsoft_endpoint = (known after apply)
+ secondary_dfs_microsoft_host = (known after apply)
+ secondary_file_endpoint = (known after apply)
+ secondary_file_host = (known after apply)
+ secondary_file_internet_endpoint = (known after apply)
+ secondary_file_internet_host = (known after apply)
+ secondary_file_microsoft_endpoint = (known after apply)
+ secondary_file_microsoft_host = (known after apply)
+ secondary_location = (known after apply)
+ secondary_queue_endpoint = (known after apply)
+ secondary_queue_host = (known after apply)
+ secondary_queue_microsoft_endpoint = (known after apply)
+ secondary_queue_microsoft_host = (known after apply)
+ secondary_table_endpoint = (known after apply)
+ secondary_table_host = (known after apply)
+ secondary_table_microsoft_endpoint = (known after apply)
+ secondary_table_microsoft_host = (known after apply)
+ secondary_web_endpoint = (known after apply)
+ secondary_web_host = (known after apply)
+ secondary_web_internet_endpoint = (known after apply)
+ secondary_web_internet_host = (known after apply)
+ secondary_web_microsoft_endpoint = (known after apply)
+ secondary_web_microsoft_host = (known after apply)
+ sftp_enabled = false
+ shared_access_key_enabled = true
+ table_encryption_key_type = "Service"
+ tags = {
+ "environment" = "staging"
}
}
# azurerm_storage_blob.examplefilestorageblob will be created
+ resource "azurerm_storage_blob" "examplefilestorageblob" {
+ access_tier = (known after apply)
+ content_type = "application/octet-stream"
+ id = (known after apply)
+ metadata = (known after apply)
+ name = "testfile1.txt"
+ parallelism = 8
+ size = 0
+ source = "./fileToUpload/testfile1.txt"
+ storage_account_name = (known after apply)
+ storage_container_name = "test"
+ type = "Block"
+ url = (known after apply)
}
# azurerm_storage_container.data will be created
+ resource "azurerm_storage_container" "data" {
+ container_access_type = "blob"
+ default_encryption_scope = (known after apply)
+ encryption_scope_override_enabled = true
+ has_immutability_policy = (known after apply)
+ has_legal_hold = (known after apply)
+ id = (known after apply)
+ metadata = (known after apply)
+ name = "test"
+ resource_manager_id = (known after apply)
+ storage_account_name = (known after apply)
}
# random_string.random will be created
+ resource "random_string" "random" {
+ id = (known after apply)
+ length = 16
+ lower = true
+ min_lower = 0
+ min_numeric = 0
+ min_special = 0
+ min_upper = 0
+ number = true
+ numeric = true
+ result = (known after apply)
+ special = false
+ upper = true
}
Plan: 5 to add, 0 to change, 0 to destroy.
run "verify_storage_account"...
As we see here, terraform test -verbose produces detailed plan which helps you current state/proposed changes for the infrastructure.
Some more useful CLI commands for Terraform test
terraform test -json – displays JSON output for your testing results
terraform test -filter=testfile – will limit the test operation on specific testfiles only
Mocking capability with Terraform test
Check out this article: https://developer.hashicorp.com/terraform/language/tests/mocking
Terraform lets you mock providers, resources, and data sources for your tests. This allows you to test parts of your module without creating infrastructure or requiring credentials. In a Terraform test, a mocked provider or resource will generate fake data for all computed attributes that would normally be provided by the underlying provider APIs.
Mocking functionality can only be used with the
[terraform testlanguage](https://developer.hashicorp.com/terraform/language/tests).
In separate article we shall discuss about mocking in detail.
In conclusion, Terraform test helps you define test cases in “easy to define” test cases manner. It is the native support provided by terraform to handle terraform configuration related test cases. There are other tools to test terraform configuration (with some advanced features like terratest, kitchen-terraform) while terraform test you can use without installing, configuring any extra tool.
Full code is placed here
https://github.com/madhubanti0007/terraform-code-samples
You can follow me in linkedin here
See you in my next article…Sayonara
메타데이터
- post_id
- b2e06ce9bc46
- slug
- terraform-test-by-terraform-a-native-testing-support-for-infrastructure-provisioning-b2e06ce9bc46
- url
- https://awstip.com/terraform-test-by-terraform-a-native-testing-support-for-infrastructure-provisioning-b2e06ce9bc46
- canonical_url
- https://awstip.com/terraform-test-by-terraform-a-native-testing-support-for-infrastructure-provisioning-b2e06ce9bc46
- author_url
- https://medium.com/@unpacktechwithmadhu
- status
- ok
- fetched_at
- 2026-08-11 15:34:11