Getting Started Using Terraform Tests with Azure example

Spread the love


I have written a lot of Terraform over the years and learned the importance of testing Terraform configurations. Testing is often overlooked but is crucial for ensuring the reliability and correctness of your infrastructure code. In this blog post, I will show you how to write Terraform Tests along with an Azure example.

Table of Contents

Why Test Terraform Code?

A solid testing strategy is essential for any form of Infrastructure as Code (IaC). Terraform is no exception.

Implementing a robust test strategy for your Terraform code can:

  • Catch errors early in the development process
  • Ensure your infrastructure meets specified requirements
  • Provide confidence when making changes
  • Serve as documentation for expected behavior

Terraform Tests Setup

I do recommend this article from HashiCorp on the terraform test command, its a great overview. I will give a very brief structure of the setup, but do check out the blog post for more detailed explanation

Terraform tests reside in dedicated test files, typically grouped together (I usually bundle a number of tests into the same file. Terraform recognizes these files by their extensions: .tftest.hcl or .tftest.json.

Example folder structure below:

  • terraform/ folder will store the Terraform configuration I want to apply
  • tests/ folder will store the .tftest.hcl files that will be used by terraform test
terraform/
    └── tests/
       └── main.tftest.hcl
    └── main.tf
    └── providers.tf
    └── variables.tf

Getting Started with Terraform Tests

Lets start by looking at a basic example, I will be following the same folder structure as above, in main.tf , I will be creating an example.txt file using local_file resource:

# create local example file

resource "local_file" "example" {
  content  = "This is an example file."
  filename = "${path.module}/example.txt"
}

Inside my Terraform tests folder, I have created main.tftest.hcl with tests:

  • plan – Checks if file will contain expected content
  • plan – Checks if file path will be example.txt
  • apply – Confirms file was created at required location
run "verify_local_file_creation" {
  command = plan

  assert {
    condition     = local_file.example.content == "This is an example file."
    error_message = "File content does not match expected value"
  }

  assert {
    condition     = local_file.example.filename == "${path.module}/example.txt"
    error_message = "File path does not match expected value"
  }
}

run "verify_file_exists" {
  command = apply

  assert {
    condition     = fileexists("${path.module}/example.txt")
    error_message = "File was not created at expected location"
  }
}

Now lets run terraform test

terraform-local-tftest-hcl % terraform test                  
tests/main.tftest.hcl... in progress
  run "verify_local_file_creation"... pass
  run "verify_file_exists"... pass
tests/main.tftest.hcl... tearing down
tests/main.tftest.hcl... pass

Success! 2 passed, 0 failed.

Success, both tests have passed!

Full code example in this GitHub respository

Real-World Testing Scenario in Azure

Recently I implemented a disaster recovery solution for Postgresql Flexible server. This solution uses GeoRestore. As part of this, I wrote a number of Terraform tests. I have rewrote some of these tests for this example.

Here is the Terraform resources:

resource "azurerm_resource_group" "tamopsrg" {
  name     = "tamops-postgres"
  location = "Uk South"
}

resource "azurerm_postgresql_flexible_server" "tamopspsql" {
  name                   = "tamops-psqlflexibleserver"
  resource_group_name    = azurerm_resource_group.tamopsrg.name
  location               = azurerm_resource_group.tamopsrg.location
  version                = "16"
  administrator_login    = "thomas"
  administrator_password = "thomasthomas123!"
  zone                   = "2"

  storage_mb = 32768

  sku_name                     = "GP_Standard_D4s_v3"
  geo_redundant_backup_enabled = true
}

resource "azurerm_postgresql_flexible_server_database" "tamopspsqldb" {
  name      = "tamopsdb"
  server_id = azurerm_postgresql_flexible_server.tamopspsql.id
  collation = "en_US.utf8"
  charset   = "utf8"
}

resource "azurerm_postgresql_flexible_server" "tamopspsqlgeorestore" {
  name                              = "tamops-psqlgeorestore"
  resource_group_name               = azurerm_resource_group.tamopsrg.name
  location                          = "Uk West"
  version                           = "16"
  create_mode                       = "GeoRestore"
  source_server_id                  = azurerm_postgresql_flexible_server.tamopspsql.id
  point_in_time_restore_time_in_utc = timeadd(timestamp(), "5m") 
}

.tftest.hcl file that has the tests that Terraform will use:

# main.tftest.hcl

# Test resource group
run "verify_resource_group" {
  command = plan

  assert {
    condition     = azurerm_resource_group.tamopsrg.name == "tamops-postgres"
    error_message = "Resource group name does not match expected value"
  }

  assert {
    condition     = azurerm_resource_group.tamopsrg.location == "uksouth"
    error_message = "Resource group location does not match expected value"
  }
}

# Test PostgreSQL Flexible Server
run "verify_postgresql_server" {
  command = plan

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsql.name == "tamops-psqlflexibleserver"
    error_message = "PostgreSQL server name does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsql.version == "16"
    error_message = "PostgreSQL version does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsql.zone == "2"
    error_message = "Availability zone does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsql.storage_mb == 32768
    error_message = "Storage size does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsql.sku_name == "GP_Standard_D4s_v3"
    error_message = "SKU name does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsql.geo_redundant_backup_enabled == true
    error_message = "Geo-redundant backup setting does not match expected value"
  }
}

# Test PostgreSQL Database
run "verify_postgresql_database" {
  command = plan

  assert {
    condition     = azurerm_postgresql_flexible_server_database.tamopspsqldb.name == "tamopsdb"
    error_message = "Database name does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server_database.tamopspsqldb.collation == "en_US.utf8"
    error_message = "Database collation does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server_database.tamopspsqldb.charset == "utf8"
    error_message = "Database charset does not match expected value"
  }
}

# Test Geo-Restore Server
run "verify_postgresql_georestore" {
  command = plan

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsqlgeorestore.name == "tamops-psqlgeorestore"
    error_message = "Geo-restore server name does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsqlgeorestore.location == "ukwest"
    error_message = "Geo-restore server location does not match expected value"
  }

  assert {
    condition     = azurerm_postgresql_flexible_server.tamopspsqlgeorestore.create_mode == "GeoRestore"
    error_message = "Create mode does not match expected value"
  }
}

Running terraform test, we can see successful test output:

terraform test
tests/main.tftest.hcl... in progress
  run "verify_resource_group"... pass
  run "verify_postgresql_server"... pass
  run "verify_postgresql_database"... pass
  run "verify_postgresql_georestore"... pass
tests/main.tftest.hcl... tearing down
tests/main.tftest.hcl... pass

Full code example in this GitHub respository

Wrapping up

Writing Terraform tests as part of your deployment can save numerous hours of debugging and even potential production issues. Start small, focus on critical resources, and gradually build up your test coverage.

Remember, even small steps towards comprehensive testing can yield substantial benefits in the long run.

Happy testing!


Share this content:

I am a passionate blogger with extensive experience in web design. As a seasoned YouTube SEO expert, I have helped numerous creators optimize their content for maximum visibility.

Leave a Comment