terraform

Terraform 1.7 adds test mocking and config-driven remove

Now generally available, HashiCorp Terraform 1.7 adds mocking capabilities to the Terraform test framework and a config-driven state removal workflow.

We’re excited to announce that HashiCorp Terraform 1.7 is now generally available, ready for download, and available for use in Terraform Cloud. Terraform 1.7 features a new mocking capability for the Terraform test framework, a new method for removing resources from state, and an enhancement for config-driven import. These additions help Terraform developers more thoroughly test their modules and gives operators safer and more efficient options for state manipulation.

»Terraform test mocking

In Terraform 1.6 we introduced the Terraform testing framework, a native option to perform unit and integration testing of your Terraform code using the HashiCorp Configuration Language (HCL). Terraform 1.7 brings several improvements to the testing framework, highlighted by the new mocking feature.

Previously, all tests were executed by making actual provider calls using either a plan or apply operation. This is a great way to observe the real behavior of a module. But it can also be useful to mock provider calls to model more advanced situations and to test without having to create actual infrastructure or requiring credentials. This can be especially useful with cloud resources that take a long time to provision, such as databases and higher-level platform services. Mocking can significantly reduce the time required to run a test suite with many different permutations, giving module authors the ability to thoroughly test their code without slowing down the development process.

Test mocking adds powerful flexibility to module testing through two primary functions: mock providers and overrides.

»Mock providers

A mocked provider or resource in a Terraform test will generate fake data for all computed attributes that would normally be provided by the underlying provider APIs. By employing aliases, mocked and real providers can be used together to create a flexible Terraform test suite for your modules.

The new mock_provider block defines a mock provider, and within this block you can specify values for computed attributes of resources and data sources. This example mocks the AWS provider and sets a specific value for the Amazon S3 bucket resource. Test runs using the mocked version of this provider will return the specified arn value for all S3 bucket resources instead of randomly generated fake data:

example.tftest.hcl
mock_provider "aws" {
  mock_resource "aws_s3_bucket" {
    defaults = {
      arn = "arn:aws:s3:::test-bucket-name"
    }
  }
}
 
run "sets_bucket_name" {
  variables {
    bucket_name = "test-bucket-name"
  }
 
  # Validates a known attribute set in the resource configuration
  assert {
    condition     = output.bucket == "test-bucket-name"
    error_message = "Wrong ARN value"
  }
 
  # Validates a computed attribute using the mocked resource
  assert {
    condition     = output.arn == "arn:aws:s3:::test-bucket-name"
    error_message = "Wrong ARN value"
  }
}

»Overrides

In addition to mocking whole providers, you can also override specific instances of resources, data sources, and modules. Override blocks can be placed at the root of a Terraform test file to apply to all test runs, or within an individual run block, and can be used with both real and mocked providers. Common use cases for overrides include cutting down test execution time for resources that take a long time to provision, child modules where you’re concerned only with simulating the outputs, or to diversify the attributes of a data source for various test scenarios. This example overrides a module and mocks the output values:

example.tftest.hcl
mock_provider "aws" {}
 
override_module {
  target = module.big_database
  outputs = {
    endpoint = "big_database.012345678901.us-east-1.rds.amazonaws.com:3306"
    db_name  = "test_db"
    username = "fakeuser"
    password = "fakepassword"
  }
}
 
run "test" {
  assert {
    condition     = module.big_database.username == "fakeuser"
    error_message = "Incorrect username"
  }
}

»Learn more about test mocking

There’s much more you can do with the new mocking capabilities of the Terraform test framework to help enhance your testing and produce higher-quality modules. To learn more, check out the Mocks documentation, and try it out by following the updated Write Terraform tests tutorial.

Along with test mocking, Terraform 1.7 includes several other enhancements to the test framework. You can now:

  • Reference variables and run outputs in test provider blocks
  • Use HCL functions in variable and provider blocks
  • Load variable values for tests from *.tfvars files.

For a deep dive on all things testing, check out the recently updated Testing HashiCorp Terraform blog post.

»Config-driven remove

During the infrastructure lifecycle, it’s sometimes necessary to modify the state of a resource. The Terraform CLI has multiple commands related to state manipulation, but these all face similar challenges: they operate on only one resource at a time, must be performed locally with direct access to state and credentials, and they immediately modify the state. This is risky because it leaves the configuration and state out of sync, which can lead to accidental resource changes. That’s why in Terraform 1.1 we introduced the concept of config-driven refactoring with the moved block, and continued this with config-driven import in Terraform 1.5. Today with Terraform 1.7, this concept has again been extended with config-driven remove.

There are several reasons why you might need to remove a resource from state without actually destroying it:

  • Moving resources between workspaces
  • Cleaning up state after apply-time failures
  • Refresh failures due to manual resource changes
  • Provider deprecations and upgrades

As an alternative to the terraform state rm command, the removed block addresses all of these challenges. Just like the moved and import blocks, state removal can now be performed in bulk and is plannable, so you can be confident that the operation will have the intended effect before modifying state. Removed blocks have a simple syntax:

removed {
  # The resource address to remove from state
  from = aws_instance.example
 
  # The lifecycle block instructs Terraform not to destroy the underlying resource
  lifecycle {
    destroy = false
  }
}

Config-driven remove is also compatible with all Terraform Cloud workflows including VCS-driven workspaces. And soon, structured run output in Terraform Cloud will be able to visually render removal actions alongside other plan activity. Read more about using removed blocks with resources and using removed blocks with modules in the Terraform documentation, and try it out with the updated Manage resources in Terraform state tutorial.

»Import block for_each

Terraform 1.7 also includes an enhancement for config-driven import: the ability to expand import blocks using for_each loops. Previously you could target a particular instance of a resource in the to attribute of an import block, but you had to write a separate import block for each instance. Now you can accomplish this with a single import block:

locals {
  buckets = {
    "staging" = "bucket-demoapp-staging"
    "uat"     = "bucket-demoapp-uat"
    "prod"    = "bucket-demoapp-prod"
  }
}
 
import {
  for_each = local.buckets
  to       = aws_s3_bucket.example[each.key]
  id       = each.value
}
 
resource "aws_s3_bucket" "example" {
  for_each = local.buckets
  bucket   = each.value
}

This technique can also be used to expand imports across multiple module instances. Learn more and see an example in the Import documentation.

»Get started with Terraform 1.7

For more details and to learn about all of the enhancements in Terraform 1.7, please review the full HashiCorp Terraform 1.7 changelog. Additional resource links include:

As always, this release wouldn't have been possible without all of the great community feedback we've received via GitHub issues and from our customers. Thank you!


Sign up for the latest HashiCorp news

By submitting this form, you acknowledge and agree that HashiCorp will process your personal information in accordance with the Privacy Policy.