Unlocking the Power of Terratest — Part 2

Daniel Weiskopf
5 min readMay 31, 2023

--

How to use terratests to provide useful insight into the functionality of our terraform modules.

In Part 1 of my Unlocking the Power of Terratest series, I discussed how terratests should not be used as simply a way to make sure that terraform is doing its job by creating the resources we declaratively define in our terraform code. Rather the tests should give us a deeper insight into the functionality of our terraform code. In part 1, we showed how we can use terratests to check the interconnectivity between separate resources within our module.

In part 2 we will discuss another important use case of terratest, to test our custom conditional logic.

Conditional Logic

When writing complex modules to be consumed by end users, a great way to simplify the use of modules is to create variables that control the conditional creation and configuration of resources. It allows you to define dynamic behavior and apply different configurations based on varying factors .Instead of requiring users to know how to configure their variables to produce a specific infrastructure configuration, everything can be controlled by a variable that a user can define with a simple true or false .

This is best explained through an example. In our example we will be deploying a simple environment that uses an EC2 instance to host an application and directs traffic through an Application Load Balancer.

But there are two distinct use cases for a module like this. One could use this module to create a public facing web application that will host the EC2 on a public subnet and use the load balancer to direct public traffic to our application. Alternatively, a module like this could also be used to host an internal application that uses an internal load balancer to direct traffic within our private network to our service.

Although switching between the two use cases of our module requires multiple changes to various resource components, we can batch those changes up so we don’t need to set each configuration individually. — For instance, for a public facing application, the subnet for the EC2 instance need to be public, the load balancer needs to be set to internal = false , and the port on the ALB listener must be set to a port used for web traffic. In order to use the module for a private application, the subnet must be private, the ALB must be set to internal = true , and the port can be set to whichever port the application is expecting.

While in theory a user can be trusted to set the configurations according to their specific needs, in practice, it is always best to remove as much responsibility from the end user as possible. We want simple, easy to use modules that can be consumed by anyone within the organization with little to no experience about the details of the infrastructure they are building.

So instead of requiring the user to configure the module to fit their use case, we will simply ask the user to decide whether they want to deploy a public application or a private one. And the way we will accept this information the user will be a simple boolean variable called private that a user can set to true or false. The rest will be taken care of by the module itself.

Another crucial benefit of this design is that it becomes a lot easier to configure our module. Instead of having to tweak multiple variables to achieve the desired state, flipping one variable from true to false can trigger all the configurations needed to change the functionality of our module. — Let’s see what this might look like in practice:

This is a snippet of code from our example module. As you can see in line 4, 5, 12, 14, and 15, we are using a terraform condition based off the private variable to decide the subnets and security groups for our infrastructure, as well as whether the load balancer will be public or internal. The custom conditional logic provides flexibility in defining your infrastructure configuration based on specific conditions, making your Terraform code more adaptable to different scenarios and requirements.

By utilizing custom conditional logic in Terraform modules, you can make your infrastructure configurations more flexible, reusable, and adaptable to different scenarios and requirements. It enables you to define dynamic behavior and tailor the infrastructure provisioning based on specific conditions or variables, enhancing the flexibility and maintainability of your infrastructure-as-code solutions.

Testing Our Conditions

Now that we understand why we are using custom conditions within our modules, it becomes clear why we must rigorously test our conditional logic to make sure that everything is working as expected. In our example above, the module was fairly simple, but in more complex modules with multiple resources, maintaining our conditional logic can be fragile. A simple change to how a resource is designed can break our conditions and ruin the functionality of our module. And because this logic is created by the module maintainer and not by terraform or our cloud provider, it is up to the maintainer to test that this logic holds up when the module is updated or changed.

That is why using terratest to test our conditional logic is so important. Terratest allows us to run mock deployments of our module to test both use cases of our module. Running the test for both values of our conditional variable will tell us whether the custom conditional logic within our module is running as expected.

In our example, we would first deploy the module with the variable private set to true to run some tests to make sure the module is behaving as expected. A good test to run would be to see whether traffic from the internet could reach our load balancer endpoint. Because we want to make sure our application is private, we would expect for traffic to be blocked.

Then we will run a new deployment of our module with the variable private set to false. Then we will run similar tests, but this time make sure that the application is in fact public and can be reached from the internet.

Conclusion

The purpose of this article was to showcase another prime example of what kind of tests we should be using terratest for. One of the challenges of using a new tool is to know what kind of problem we are trying to solve and how we can get the most utility out of our tooling.

In the last article, Part 1 in the series, I explained why terratests are more useful than running a simple create and destroy on a terraform module because terratest can verify that the larger infrastructure as a whole is working properly. In this article, Part 2, I explained another important use for testing modules with terratest. Because modules often include custom conditional logic, only terratest can create custom testing implementations to check for custom logic.

In Part 3 of the Unlocking the Power of Terratest series I will discuss the various ways of organizing a terratest workflow that can be used to verify the continuous functionality of our terraform modules. Thank for reading and stay tuned!

--

--