Introduction#
The Terraform test framework has been around for more than one year, and I have previously written extensively on the topic.
A part of the test framework that I have not covered is what was introduced in Terraform 1.7. Using Terraform test mocks allow you to mock providers, resources and data sources in your test files.
In this blog post I will go through the additions that Terraform 1.7 brought to the Terraform test framework.
What are Terraform test mocks?#
A mock in the Terraform test framework is similar to a mock in any other test framework. Mocking is the practice of replacing an actual instance of something with a fake, or mocked, instance of that something. In the context of the Terraform test framework the “something” is a provider, a module, a resource or a data source.
Why would you want to, or need to, mock something in your tests?
Ideally you should run full tests using real resources, data sources and providers. This is the only way you will ever be sure that everything works end-to-end. However, using mocks can significantly speed up your tests.
A concrete example where mocking makes sense is if you have a large infrastructure, perhaps with multiple Kubernetes clusters, and you want to run a test related to a load balancer that directs traffic to one of your Kubernetes clusters. In this case it could make sense to mock some, or all, of the Kubernetes clusters.
Note that in some cases it might not be possible to mock away certain resources or data sources. This is true when you want to run tests for a resource that has dependencies to other resources. In this case it might not be possible to replace the dependencies with mocks.
Apart from pure mocks the Terraform test framework also supports overrides. In the following subsections we will see how both of these work.
Mock a provider#
If you want to mock a provider you use the new mock_provider
block in your Terraform test files:
mock_provider "azurerm" {}
In this example we have mocked the azurerm
provider. All interactions with this provider will be replaced by dummy data.
You can mix real and mocked providers of the same type in the same test configuration. An example of how to do this:
provider "azurerm" {}
mock_provider "azurerm" {
alias = "mocked"
}
As with a normal Terraform configuration you need to provide an alias
for any additional provider configurations you include. In your tests you can either use the default provider (the provider with no alias
argument), or specify that a different provider should be used:
run "test_with_mocked_provider" {
providers = {
azurerm = azurerm.mocked
}
}
Mock a resource#
If you want to be more specific about what data a mocked provider returns you can mock a specific resource using the mock_resource
block:
mock_provider "azurerm" {
mock_resource "azurerm_resource_group" {
defaults = {
name = "rg-test-group"
}
}
}
The mock_resource
block accepts a defaults
argument where you can specify what data should be returned when a resource of the mocked type is used. In the example above we specify that the name
attribute of the azurerm_resource_group
resource should be returned as rg-test-group
.
Note that the code above mocks all instances of azurerm_resource_group
resources.
Mock a data source#
Similar to how you mock a resource you can also mock a data source using the mock_data
block:
mock_provider "azurerm" {
mock_data "azurerm_resource_group" {
defaults = {
name = "rg-test-group"
}
}
}
The mock_data
block accepts the same default
argument as the mock_resource
block.
Note that as with mocked resources, the code above mocks all instances of the azurerm_resource_group
data source.
Overrides#
Mocks replace all instances of a given resource type or data source type. This might not be desirable for some tests. For these situations the Terraform test framework supports overrides.
An override is similar to a mock, but they allow you to replace a specific instance of a module, resource or data source. This allows you to use a real provider but specifically target a few objects and replace them with mocks.
To override a data source, use the override_data
block:
mock_provider "azurerm" {
override_data {
target = data.azurerm_resource_group.this
values = {
name = "rg-overridden-name"
}
}
}
All override blocks take a target
argument that specifies the address of the object to be overridden.
To override a resource, use the override_resource
block:
mock_provider "azurerm" {
override_resource {
target = data.azurerm_resource_group.this
values = {
name = "rg-overridden-name"
}
}
}
Both the override_data
and override_resource
block accepts a values
argument where you can configure specific values for attributes on the object.
Finally, you can override a whole module with the override_module
block:
override_module {
target = module.networking
outputs = {
cidr = "10.100.10.0/24"
}
}
The override_module
block has an outputs
argument where you can override the output values from this module. Note that the override_module
is defined in the root of a test file, not as part of the mock_provider
block. In fact, you can also define override_resource
and override_data
blocks in the root of the test file:
override_data {
target = data.azurerm_resource_group.this
values = {
name = "rg-overridden-name"
}
}
override_resource {
target = data.azurerm_resource_group.this
values = {
name = "rg-overridden-name"
}
}
override_module {
target = module.networking
outputs = {
cidr = "10.100.10.0/24"
}
}
run "this_is_my_test" {
# ...
}
You can also define override blocks inside run
blocks. Overrides defined in a given run
block will take precedence over any other override block for the same module, resource or data source:
# this block is defined at the root (or global) level
override_data {
target = data.azurerm_resource_group.this
values = {
name = "rg-overridden-name"
}
}
run "this_is_my_test" {
# this block is defined inside of a run block and it will take precedence
# in this specific test
override_data {
target = data.azurerm_resource_group.this
values = {
name = "rg-overridden-name"
}
}
}
Conclusions#
Terraform test mocks can help you speed up your test runs. They allow you to focus on what is important in your current test and allow you to skip the resources and data sources that do not add anything to the test.
There are a number of new blocks available for your Terraform test files to work with mocks:
mock_provider
to mock a given providermock_resource
to mock all resources of a specific typemock_data
to mock all data sources of a specific typeoverride_module
to mock a specific moduleoverride_resource
to mock a specific resourceoverride_data
to mock a specific data source
Use mocks sparingly and only where it makes sense to do so.
Read more about mocks in the official documentation.