We have seen checks that analyze the code without executing it (fmt, validate, Checkov, tfsec). They are fast and useful, but they have a limit: they do not check that your infrastructure actually works once created. For that, there are integration tests, and the most well-known tool in the Terraform world is Terratest. In this subsection, you will understand what they provide and how they work in broad strokes.

The limit of static analysis

The tools from the previous subsections read your code, but do not create anything. They can tell you "this code looks correct and safe," but they cannot answer questions like:

  • Does the EC2 instance actually start up and respond?
  • Does the web server return the expected page?
  • Does the load balancer distribute traffic correctly?
  • Do the resources connect to each other as they should?

To answer this, it is not enough to read the code: you have to actually create the infrastructure and test it. That is an integration test.

What is an infrastructure integration test

An integration test follows this cycle: create the real infrastructure (in a test environment), verify that it works as expected, and then destroy it so nothing is left behind and you stop paying.

1. CREATE    → terraform apply (sets up the real infra in a test account)
2. VERIFY    → check that it works (does it respond? is it properly configured?)
3. DESTROY   → terraform destroy (cleans everything up, stops paying)

Analogy: static analysis is like reviewing the blueprints of a car; integration testing is like building a prototype and driving it on a test track to see if it really starts, brakes, and turns. After the test, the prototype is dismantled. It's more expensive than looking at the blueprints, but it gives you a certainty that paper cannot provide.

What is Terratest

Terratest is a Go library (created by Gruntwork) for writing infrastructure integration tests. With it, you write a small program in the Go language that automates the entire cycle: applies your Terraform, performs checks, and destroys the infrastructure at the end.

Terratest (in Go) automates:
  terraform apply  →  checks  →  terraform destroy

A test with Terratest, broadly speaking, does something like this (you don't need to master Go to get the idea):

// Simplified pseudo-example of a test with Terratest
func TestWebServer(t *testing.T) {
    // 1. Apply the Terraform
    terraform.InitAndApply(t, options)

    // 3. Ensure it is destroyed at the end (even if the test fails)
    defer terraform.Destroy(t, options)

    // 2. Verify: get the IP from the output and check that the web responds
    ip := terraform.Output(t, options, "public_ip")
    http.Get("http://" + ip)   // Does it respond 200 OK with the expected content?
}

Notice the pattern:

  • Apply: creates the infrastructure (uses your real Terraform code).
  • Verify: reads the outputs (remember subsection 12.4) and checks real things, like whether the web server responds.
  • Destroy (with defer): ensures cleanup always happens, even if the test fails. This is crucial to avoid leaving costly resources forgotten.

What you can verify with Terratest

Terratest allows you to check that the infrastructure actually works:

  • That a website responds with the expected code and content.
  • That a server is accessible via SSH or a port.
  • That a database accepts connections.
  • That the outputs of Terraform have the correct values.
  • That a module (Chapter 18) creates exactly the resources it promises.

It is especially useful for testing reusable modules: before publishing a new version of your module (subsection 18.4), an integration test confirms that it still works.

The trade-off: powerful but costly

Integration tests are the most comprehensive, but also the most costly, and it is important to be aware of this:

Static analysis (fmt, validate, Checkov) Integration tests (Terratest)
Creates real infrastructure No Yes
Speed Seconds Minutes (creates and destroys)
Cost Free (creates nothing) Costs money (real resources)
What it tests That the code is correct/safe That the infra actually works
Requires knowledge of Basic commands Programming in Go

That's why they are not run on every trivial change: since they create real resources (takes minutes and costs money), integration tests are usually reserved for important changes, to validate modules before publishing them, or are run periodically (for example, nightly), instead of on every small commit.

Do I need this to get started?

Not at first. The testing pyramid makes sense in order of cost/benefit:

        ▲  Few: integration tests (Terratest) — costly, for important things
       ╱ ╲
      ╱   ╲ Some: security analysis (Checkov/tfsec)
     ╱─────╲
    ╱       ╲ Many: fmt + validate — cheap, on every change
   ╱─────────╲

Start with the base (fmt, validate), add security (Checkov/tfsec), and reserve integration tests for when your infrastructure is critical or you publish reusable modules that many will use. You don't need Terratest from day one, but it's good to know it exists and what problem it solves.

What you should remember

  • Static analysis (fmt, validate, Checkov) reads the code but does not check that the infrastructure actually works; that's what integration tests are for.
  • An integration test follows the cycle create → verify → destroy: sets up the real infra in a test account, checks that it works, and deletes it. Like building and driving a prototype, not just looking at the blueprints.
  • Terratest is a Go library that automates that cycle (apply → checks → destroy), ensuring cleanup always happens (with defer destroy), even if the test fails.
  • It allows you to verify real things: that a website responds, that a server is accessible, that the outputs are correct, that a module creates what it promises (ideal for validating modules).
  • They are the most comprehensive tests but also the most costly (create real resources, take minutes, cost money, require Go); they are reserved for the important stuff, not for every trivial change. Start with the base of the pyramid.

In the last subsection of the chapter, we will see a technique to ensure that modules fit well together: contract testing between modules.

Cloud, AWS & Terraform — From Zero to Expert

Chapter 1 · What is cloud computing

Chapter 2 · The cloud market and major providers

Chapter 3 · Regions, availability zones and edge

Chapter 4 · Compute: EC2

Chapter 5 · Storage: S3

Chapter 6 · Networking: VPC

Chapter 7 · Identity and access: IAM

Chapter 8 · Managed databases

Chapter 9 · Why Infrastructure as Code

Chapter 10 · HCL: the Terraform language

Chapter 11 · Providers and state

Chapter 12 · Your first real infrastructure in Terraform

Chapter 13 · Load balancing and auto scaling

Chapter 14 · Serverless with Lambda

Chapter 15 · Messaging and events

Chapter 16 · Content delivery and DNS

Chapter 17 · Containers on AWS

Chapter 18 · Modules: reuse and composition

Chapter 19 · Workspaces and environment management

Chapter 20 · Remote backends and locking

Chapter 21 · Infrastructure testing

Chapter 22 · Terraform in CI/CD

Chapter 23 · Defense in depth

Chapter 24 · Observability: logs, metrics and traces

Chapter 25 · Cost optimization

Chapter 26 · High availability and disaster recovery

Chapter 27 · AWS Well-Architected Framework

Chapter 28 · Serverless architectures at scale

Chapter 29 · Data platforms on AWS

Chapter 30 · Multi-account and landing zones

Chapter 31 · Platform Engineering and Internal Developer Platform

Chapter 32 · Relevant AWS certifications

Chapter 33 · Projects to consolidate what you've learned

Chapter 34 · Resources and community

© Copyright 2024. All rights reserved