All jobs

Write Terraform Tests for a Terraform Module

Yes. An agent writes .tftest.hcl files that plan your module against mock providers, runs terraform test with no cloud credentials and opens a PR.

Run this on RushIn a Project that has your repository.

What the agent does

In a module like terraform-aws-modules/terraform-aws-vpc, the agent starts with what already checks the code. .pre-commit-config.yaml runs terraform_fmt, terraform_validate, terraform_docs and tflint with a list of rules such as terraform_typed_variables and terraform_documented_outputs, and .github/workflows/pre-commit.yml runs those hooks on every pull request, once at the minimum Terraform version and once at the latest. None of that evaluates the module's logic, and there is no .tftest.hcl file anywhere in the repository. That is the gap the tests fill.

Next it reads where the logic lives. main.tf computes len_public_subnets, len_private_subnets and the other per-tier lengths, takes max_subnet_length across them, and derives nat_gateway_count as 1 when single_nat_gateway is set, length(var.azs) when one_nat_gateway_per_az is set, and max_subnet_length otherwise. The route tables, EIPs and NAT gateways all count off those locals. These are the expressions a refactor breaks silently, so each one gets a run block: three private subnets across three zones with each NAT mode, asserting length(aws_nat_gateway.this) and length(aws_eip.nat), plus the CIDR of a subnet built with cidrsubnet the way examples/simple/main.tf does.

The tests run with command = plan and a mock_provider "aws" block, so no AWS account is needed. That has two consequences the agent handles explicitly. Values AWS assigns at apply time, like resource ids, are unknown in a plan, so assertions stick to counts, CIDR blocks and tags that come straight from input variables. And data sources return generated values under a mock, so where examples read data.aws_availability_zones.available, the test passes azs as a variable or uses override_data, and the flow log policies built from aws_iam_policy_document in vpc-flow-logs.tf are not asserted on.

Last is versioning. versions.tf says required_version = ">= 1.0" and the AWS provider >= 6.28, but terraform test needs 1.6 and mock_provider needs 1.7. The agent leaves the module's constraint alone, puts the tests under tests/, which terraform validate does not read, so the minimum-version CI job keeps working, runs terraform init and terraform test on a current Terraform, and states the 1.7 requirement for the tests in the pull request.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Write terraform test files (.tftest.hcl) under tests/ for this VPC module. Use command = plan and a mock_provider for aws so they run with no AWS credentials. Cover the subnet count locals, CIDR math with cidrsubnet, each NAT gateway mode (single_nat_gateway, one_nat_gateway_per_az and the default), create_vpc = false creating nothing, and tags merging onto resources. Assert only on values known at plan time; pass availability zones as variables or override data sources. Keep required_version as it is. Run terraform init and terraform test, include the output, and open a pull request that lists each test and what it checks, and the Terraform version the tests need.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

The pull request adds a tests/ directory with a handful of .tftest.hcl files grouped by topic: subnets and CIDRs, NAT gateway modes, the disabled module, and tags. Each file declares mock_provider "aws" once and a run block per case with its own variables and assert blocks whose error_message says what broke, such as the expected number of NAT gateways for single_nat_gateway. The description includes the terraform test output from the sandbox, showing each run block by name, and notes the Terraform version the tests need.

A reviewer checks that each assertion is about the module's own logic rather than the mock's generated values, that the variables in each run block describe a configuration someone would actually use, and that no file under the module root changed. Running terraform test locally needs no credentials, so the reviewer can confirm the result on their own machine before merging. Adding a terraform test step to the existing workflow is listed as a follow-up rather than done silently.

Run it on every module without tests

If you maintain several modules, list the ones with no tests/ directory and send one POST /o/{org}/sessions request for each, with the module's repository in resources. Each run reads that module's own locals and variables, so a VPC module and an IAM module get tests about their own logic, not a shared template.

You can also start a run when a pull request changes a module's main.tf, asking the agent to add a run block for the new behaviour. A webhook on your key reports when each run finishes, and the session holds the pull request link and the terraform test output.

The use case this job follows, with its automation and trigger: Write the missing tests.

Why run it on Rush?

Writing Terraform tests is mostly trial and error: a plan-time assertion that turns out to be unknown, a data source the mock fills with nonsense, a provider version init cannot resolve. In a Rush sandbox the agent installs the Terraform version the tests need, runs terraform test as many times as it takes, and every init and test command is kept with its output, so the pull request shows tests that actually passed.

Because the tests use mock providers, the sandbox never holds cloud credentials, which is the right default for work that only needs a plan. Many modules can be covered in parallel, each in its own run, and each returns a pull request you can check by running terraform test yourself.

Frequently asked questions

Do the tests need AWS credentials?

No. Every file uses mock_provider for aws and command = plan, so terraform test runs offline after init downloads the provider. That is why assertions are limited to values known at plan time.

My module still supports Terraform 1.0. Will the tests break that?

No. The tests live in tests/ and only terraform test reads them, so terraform validate at the minimum version is unaffected. Running the tests needs Terraform 1.7 or newer, and the pull request says so.

Why not run the examples with terraform apply?

Apply-based tests create real VPCs and NAT gateways, which need an account, cost money and take minutes to tear down. Plan-only tests with mocks catch the count and CIDR mistakes that refactors cause, in seconds. You can ask for apply-based tests in a separate run that names a test account.

Will it add terraform test to my CI?

Only if you ask. The default pull request adds test files and suggests the workflow step, because CI changes in a public module usually need their own review.