Terraform and OpenTofu Testing: tofu test, validate and Terratest

· 2 min read · Terraform & OpenTofu Tutorials

Test your infrastructure code before it reaches production #

Infrastructure code deserves tests like any other code. The tests go from cheap and fast to expensive and realistic. Use the cheap ones on every commit and the expensive ones before releasing a module.

1. Format and validate #

$ tofu fmt -check -recursive
$ tofu init -backend=false
$ tofu validate

fmt checks the style and validate checks syntax and internal consistency (types, references) without contacting any cloud. Both take seconds.

2. Lint and security scan #

tflint finds errors that validate cannot, such as an invalid EC2 instance type. Security scanners find insecure configurations. See security scanning.

3. Native tests: terraform test and tofu test #

Terraform 1.6 and OpenTofu 1.6 added a built-in test framework. Test files end in .tftest.hcl (or .tofutest.hcl) and live next to the module or in a tests/ directory.

tests/network.tftest.hcl
variables {
  vpc_cidr = "10.10.0.0/16"
}

run "creates_vpc_with_expected_cidr" {
  command = plan

  assert {
    condition     = aws_vpc.main.cidr_block == "10.10.0.0/16"
    error_message = "Unexpected VPC CIDR."
  }
}

run "rejects_invalid_cidr" {
  command = plan

  variables {
    vpc_cidr = "not-a-cidr"
  }

  expect_failures = [var.vpc_cidr]
}

Run them:

$ tofu test
tests/network.tftest.hcl... in progress
  run "creates_vpc_with_expected_cidr"... pass
  run "rejects_invalid_cidr"... pass
tests/network.tftest.hcl... tearing down
tests/network.tftest.hcl... pass

Success! 2 passed, 0 failed.
  • command = plan only plans, which is fast and free.
  • command = apply (the default) creates the real infrastructure, runs the assertions and destroys it at the end. Use a sandbox AWS account.
  • expect_failures checks that a validation, precondition or check fails when it should.

Mock providers #

Newer versions (Terraform 1.7 and OpenTofu 1.8 or later) can replace a provider with a mock, so the tests run without credentials or cost:

tests/mock.tftest.hcl
mock_provider "aws" {}

run "plan_with_mock" {
  command = apply

  assert {
    condition     = length(aws_subnet.private) == 3
    error_message = "Expected 3 private subnets."
  }
}

Check the documentation of your version for the exact syntax and limitations of mocks.

4. Integration tests with Terratest #

Terratest is a Go library that applies the code, checks the real infrastructure (an HTTP request, an AWS API call) and destroys it:

test/vpc_test.go
func TestVpc(t *testing.T) {
	opts := &terraform.Options{TerraformDir: "../examples/basic", TerraformBinary: "tofu"}
	defer terraform.Destroy(t, opts)
	terraform.InitAndApply(t, opts)

	vpcID := terraform.Output(t, opts, "vpc_id")
	assert.NotEmpty(t, vpcID)
}

Use it when you need to test behavior, not just configuration.

Stage Tool When
Format and validate fmt, validate Every commit
Lint and security tflint, Checkov, Trivy Every pull request
Unit tests test with plan or mocks Every pull request
Integration test with apply or Terratest Before releasing a module, on a schedule

Run them in GitHub Actions and read about debugging Terraform when a test fails.

#Terraform #OpenTofu #Testing