Terraform
Mocks
Note: Test mocking is available in Terraform v1.7.0 and later. This feature is in beta.
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 test
language. Readers of this documentation should be familiar with the testing syntax and language features.
In addition, the more advanced features of the mocking framework require knowledge about the following Terraform provider features:
- The difference between attributes, nested attributes, and blocks.
- Definitions of optional, required, and computed attributes.
Mock Providers
In Terraform tests, you can mock a provider with the mock_provider
block. Mock providers return the same schema as the original provider and you can pass the mocked provider to your tests in place of the matching provider. All resources and data sources retrieved by a mock provider will set the relevant values from the configuration, and generate fake data for any computed attributes.
Mock providers can be used directly in place of traditional provider
blocks and they share the same global namespace. During the execution of the terraform test
command, Terraform does not distinguish between a real and a mocked provider.
The following example creates an AWS S3 bucket and then uses a mocked provider to test the configuration without requiring an AWS account.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
variable "bucket_name" {
type = string
}
resource "aws_s3_bucket" "my_bucket" {
bucket = var.bucket_name
}
# bucket_name.tftest.hcl
mock_provider "aws" {}
run "sets_correct_name" {
variables {
bucket_name = "my-bucket-name"
}
assert {
condition = aws_s3_bucket.my_bucket.bucket == "my-bucket-name"
error_message = "incorrect bucket name"
}
}
From the perspective of a plan
or apply
operation executed in a Terraform test file, the mocked provider is creating actual resources with values that match the configuration. These resources are stored in the Terraform state files that terraform test
creates and holds in memory during test executions.
You can use mocked providers and real providers simulating in a Terraform test. The following example defines two AWS providers, one real and one mocked. You must provide an alias to one of them, as they share the same global aws
provider namespace. You can then use the providers
attribute in test run
blocks to customize which AWS provider to use for each run
block.
# mocked_providers.tftest.hcl
provider "aws" {}
mock_provider "aws" {
alias = "fake"
}
run "use_real_provider" {
providers = {
aws = aws
}
}
run "use_mocked_provider" {
providers = {
aws = aws.fake
}
}
Generated data
A mocked provider will generate data for any computed attributes in referenced data sources or attributes. For example, the arn
attribute is a unique identifier generated by AWS for most resources. A mocked aws
provider will provide a value for this attribute in any resources it creates.
Note: Mocked providers do not have any information about the expected format of the computed attributes, so the generated data will rarely match any expected syntax that real providers would return for those attributes.
Mocked providers only generate data for computed attributes. All required resource attributes must be set when you use a mocked provider. If you do not provide a value for an optional computed attribute, Terraform will automatically generate one. The values that Terraform generates depends on the data type:
- Numbers will be 0.
- Booleans will be false.
- Strings will be a random 8 character alphanumeric string.
- Collections, including sets, lists, and maps, will be empty collections.
- Objects will contain all required sub-attributes generated using this same set of rules recursively.
An example of this is the bucket
attribute in the aws_s3_bucket
resource. A real AWS provider will generate a bucket name if one is not specified. A mocked AWS provider will do the same, and only generate a value if one is not already specified in the configuration.
Mock Provider data
You can specify specific values for targeted resources and data sources. in a mock_provider
block, you can write any number of mock_resource
and mock_data
blocks. Both the mock_resource
and mock_data
blocks accept a type argument that should match the resource or data source you want to provide values for. They also accept a defaults
object attribute that you can use to specify the values that should be returned for specific attributes.
The following example demonstrates providing a set arn
value for all AWS S3 bucket resources and data sources:
mock_provider "aws" {
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_data "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
}
In the above example, Terraform uses the supplied value for arn
attributes in S3 buckets instead of generating a random string. Computed attributes not provided an explicit default will simply fall back to the generic data generation rules.
You can also share mock provider data between tests by writing dedicated mock data files and using the source
attribute in the mock_provider
block. Mock data files have .tfmock.hcl
or .tfmock.json
extension, and can contain mock_resource
and mock_data
blocks as if they were defined in the mock_provider
block directly.
# ./testing/aws/data.tfmock.hcl
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_data "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_provider "aws" {
source = "./testing/aws"
}
The above example defines mock_resource
and mock_data
blocks in a mock data file located at ./testing/aws
. You can load multiple test files and share the same mock provider data without copying the same definitions across multiple files.
You can combine the source
attribute with directly nested mock_resource
and mock_data
blocks. If the source location and a directly nested block describe the same resource or data source then the directly nested block takes precedence.
Overrides
In addition to mocking providers, you can use the following block types to override specific resources, data sources, and modules:
override_resource
: Override the values of a resource. Terraform does not call the underlying provider.override_data
: Override the values of a data source. Terraform does not call the underlying provider.override_module
: Override the outputs of a module. Terraform does not create any resource in the module.
All three blocks can be placed at the root level of Terraform test files and in Terraform test file run
blocks. In addition, the override_resource
and override_data
blocks can be nested in mock_provider
blocks and in Terraform mock data files.
Overrides can be used with both real and mocked providers and will provide the computed values instead of the underlying provider.
Overrides Syntax
All override blocks contain a target
attribute, which should specify the resource, data source, or module to override. The override_module
blocks contain an outputs
attribute, while the override_resource
and override_data
blocks contain a values
attribute.
The outputs
and values
attributes are optional and if not specified, Terraform will generate values for them automatically.
The following example demonstrates the override blocks at various different scopes and levels. The main configuration calls the ./modules/s3_data
module to read a file from an S3 bucket, and then creates a local_file
from the data returned from the module.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
module "credentials" {
source = "./modules/s3_data"
data_bucket_name = "my_company_bucket_name"
}
resource "local_file" "credentials_json" {
filename = "credentials.json"
content = jsonencode(module.credentials.data)
}
# ./modules/s3_data/main.tf
variable "data_bucket_name" {
type = string
}
data "aws_s3_object" "data_bucket" {
bucket = var.data_bucket_name
key = "credentials.json"
}
output "data" {
value = jsondecode(data.aws_s3_object.data_bucket.body)
}
Firstly, you can override the aws_s3_bucket_object
in the module directly in a mock provider. The following example defines an override_data
block in the mock_provider
block. In this case, Terraform will only override the target data source if the mock provider creates it.
# main.tftest.hcl
mock_provider "aws" {
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"username\",\"password\":\"password\"}"
}
}
}
run "test" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
Alternatively, you could also override the same aws_s3_bucket_object
in the test file itself, or in a run
block. In this case, the underlying provider doesn't matter since Terraform overrides the data source at the target address regardless of the provider. The following example still mocks the provider so that Terraform can run the test without AWS credentials. Override blocks can override resources provided by real providers.
# main.tftest.hcl
mock_provider "aws" {}
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"username\",\"password\":\"password\"}"
}
}
run "test_file_override" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
run "test_run_override" {
# The value in this local override block takes precedence over the
# alternate defined in the file.
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"a_different_username\",\"password\":\"password\"}"
}
}
assert {
condition = jsondecode(local_file.credentials_json.content).username == "a_different_username"
error_message = "incorrect username"
}
}
In the above example, the override block defined at the file level is used in the first run
block. Then in the second run
block, the alternative local override takes precedence over the file level override.
Finally, you can override the entire module with the override_module
block. In the following example, Terraform overrides the entire module instead of specific resources in the module.
# main.tftest.hcl
mock_provider "aws" {}
override_module {
target = module.credentials
outputs = {
data = { username = "username", password = "password" }
}
}
run "test" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
In this case, the override block specifies outputs
instead of values
and the output value is specified as HCL instead of as a string as the real module is passing the data through the jsondecode
function. As with override_data
blocks, you can also specify the override_module
block in the run
block and the same precedence rules apply.
Repeated blocks and nested attributes
Some resources and data sources specify repeated nested attributes as computed directly, while repeated blocks in the resource may also contain computed attributes. For repeated blocks and nested attributes, you cannot specify values for specific instances in the collection. Instead, you must provide a single set of values that apply for all instances in the collection.
For example, the aws_dynamodb_table
resource contains a root-level computed arn
attribute as well as a set-repeated block called replica
which also contains a computed arn
attribute. The following is an example of a simple instance of an aws_dynamodb_table
.
# main.tf
resource "aws_dynamodb_table" "my_table" {
name = "my_table"
hash_key = "key"
attribute {
name = "key"
type = "S"
}
replica {
region_name = "eu-west-2"
}
replica {
region_name = "us-east-1"
}
}
Normally, the AWS provider would return ARN values for the DynamoDB table and both of the specified replicas. While a mock_resource
block can mimic this behavior, it cannot differentiate between the multiple replica
blocks in the resource:
mock_resource "aws_dynamodb_table" {
defaults = {
arn = "aws:dynamodb:::my_table"
replica = {
arn = "aws:dynamodb:::my_replica"
}
}
}
In this case, the mock_resource
provides a specific value for the ARN of the DynamoDB table, but the ARN values returned for the replica tables are shared between all instances.