TDD for Terraform IaC — Introducing t2d2

Sahu
5 min readMar 1, 2022
Gokarna, India — photosbysaurav on Instagram

“Testing Infrastructure as Code should be as easy as testing Application Code”

I began my Cloud journey in college, working on one of the projects for our Labs. It was a Xamarin Mobile Application which helped friends split expenses and the contacts were brought in through your Facebook Connections. It was the first time I used any Cloud Service Provider for hosting the backend APIs and using an Authentication Provider (Azure Active DIrectory in this case).

The process of setting up these services, at least, for me, was a little unclear as I was doing a lot of trial and error. But things did work at the end.

Infrastructure as Code has become the standard way of provisioning resources in the cloud these days, and as such, tools like Terraform, Amazon CDK and Pulumi, and obviously countless others have boomed in their popularity. And rightfully so, as long as things can be versioned and maintained in source control; and can be tested — they’re the best way to create infrastructure.

Writing Infrastructure as Code A.K.A IaC

As mentioned above, there are lots of different ways to go about IaC, and each have their reasoning for approaching it the way they have. I personally like Terraform, as it is declarative and has a fairly simple language construct.

Testing IaC

As with Application Development, Infrastructure should also contain tests to validate various aspects of said Infrastructure written as code. There are blogs about this and I’d recommend going through Rohit’s Terraform Testing Comparison blog.

As the blog goes on to say, there are various tiers of Infrastructure that you’d want to test. Few of them, already built into Terraform and then few more can be done from the plethora of libraries out there.

I come from an application development background and want to approach IaC testing with the same set of tools and construct that make web development fun, TDD.

Introducing T2D2

I’m happy to announce the first public preview of T2D2 — an abbreviation of ‘Terraform Test Driven Development’ — which is a TypeScript library to help you write IaC tests in TypeScript or JavaScript with any Testing Framework out there. These include (but are not limited to…) Jest, UVU, Mocha, Karma, etc.,.

Link to Github: https://github.com/mrsauravsahu/t2d2

The reason for creating a new testing helper library to do this is to make the transition from Application Development into IaC, easier. The testing frameworks currently available for the node ecosystem also bring in a lot of awesome features which makes testing fun. Some of them are —

  • parallel run of unit tests
  • typings for terraform (this is a WIP and I’m adding support for more Terraform versions)
  • helpers from the testing framework like object equality, pattern matching and snapshot testing

Well, how can you get started?

The way I wanted to design T2D2 is to try and make the experience as close to application development as possible. As such, let’s start creating a jest test project. (I’m going to use jest here, as it’s the library I personally use to test apps. )

1. Pre reqs

Your machine should have node.js and terraform installed. Wink wink 😉, cli-config comes with nvm and tfenv, which sets up both of these for you.

2. Starter project

Let’s create an empty directory for our project.

We can now scaffold the jest project.

$ npm init @t2d2/jest-starter

3. Folder structure

The files in the project folder should be fairly familiar. The main npm project, some configs for TypeScript and jest, the main __tests__ folder for writing your tests and tf folder for adding your Terraform Code.

You can see the jest project all setup.

❯ ls -1              
__tests__
jest.config.ts
jest.setup.ts
package-lock.json
package.json
tf
tsconfig.json

4. Check if all’s good

Install the dependencies and run the tests to check if you’re good to start TDD cycles.

$ npm i && npm t # <-- run this> placeholder@1.0.0 test
> jest
...Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/time versions matching "0.7.2"...
- Installing hashicorp/time v0.7.2...
- Installed hashicorp/time v0.7.2 (self-signed, key ID 34365D9472D7468F)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
<https://www.terraform.io/docs/cli/plugins/signing.html>

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
...An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# time_sleep.wait will be created
+ resource "time_sleep" "wait" {
+ create_duration = "1s"
+ id = (known after apply)
}

Plan: 1 to add, 0 to change, 0 to destroy.
...{"format_version":"0.1",... at node_modules/@t2d2/core/dist/plan.js:56:34 PASS __tests__/resources.ts (63.393 s)
resources
✓ your tests go here (3 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 65.572 s
Ran all test suites.

The Approach to TDD in IaC

T2D2 (at least currently) helps with the Unit Testing aspect of IaC. The approach is this — given the terraform plan succeeds, the tests should try to assert conditions in the resulting calculated terraform state.

An example to explain this —

Let’s say we our writing terraform code to create a VM, and an associated disk (I’m using CSP agnostic terms here). Your unit tests (whilst doing TDD cycles) should check if

  • if the VM and disk resources exist.
  • if the disk is associated to the VM.

You can also enforce security measures through test like encryption for the disk.

Matcher APIs

T2D2 is influenced by @testing-library and the matchers available also behave similarly. The matchers available — getResourceByAddress, getRootModuleResourceOfType, getModuleByAddress can be directly used with any assertion library.

The way forward

The idea of this article is to bring about more discussions in the IaC testing space and obviously showcase t2d2, which I’m excited to see bring used in various infra projects.

I’ll be opening up the source code for t2d2 to encourage contributions and happy to take any feedback through Github issues and improvements through pull requests.

Connect with me on Twitter @mrsauravsahu if you’re excited about this.

-S

--

--

Sahu

Personal Opinions • Beyond Full Stack Engineer @ McKinsey & Company