Oct 12, 20265 min read

How to use terraform test

Test Terraform modules locally and in CI with assertions and mocked providers.

Terraform, by itself, can validate configuration syntax and generate a valid plan without confirming that a module behaves as intended but things such as input rules, resource attributes, outputs, and expected failures need separate tests, especially when modules are reused across projects.

This guide shows how to test an AWS module with Terraform's native test framework, covering plan-based tests, mocked providers, tests that create real infrastructure, and running the suite in CI.

We'll also look at how Encore lets you test application workflows against local databases, queues, and buckets, while Terraform tests continue to cover your shared cloud infrastructure.

Add a first Terraform test

Suppose a reusable module creates the bucket used by an orders service. The configuration accepts the application and environment names, then exposes the final bucket name:

# variables.tf variable "application" { type = string } variable "environment" { type = string validation { condition = contains(["development", "staging", "production"], var.environment) error_message = "environment must be development, staging, or production" } }
# main.tf resource "aws_s3_bucket" "receipts" { bucket = "${var.application}-${var.environment}-receipts" } output "bucket_name" { value = aws_s3_bucket.receipts.bucket }

Terraform loads tests from tests/ by default, so the first test can live at tests/bucket.tftest.hcl. With the provider mocked, Terraform still evaluates the AWS resource against its schema without needing AWS credentials or creating the bucket.

mock_provider "aws" {} run "development_bucket_name" { command = plan variables { application = "orders" environment = "development" } assert { condition = aws_s3_bucket.receipts.bucket == "orders-development-receipts" error_message = "The receipt bucket name does not include the application and environment" } assert { condition = output.bucket_name == "orders-development-receipts" error_message = "The module does not return the receipt bucket name" } }

After terraform init has installed the providers referenced by the configuration, run the suite from the module root:

terraform test

Each run block receives its own variables and executes in file order unless the test file enables parallel execution. command = plan is useful when the assertions only depend on configuration and planned values; without it, a run uses apply, which is necessary when an assertion depends on values available only after creation.

Test failures and input validation

Tests can cover invalid configuration without accepting any failed plan by using expect_failures to name the validation, precondition, postcondition, check, variable, or output expected to report an error:

run "rejects_unknown_environment" { command = plan variables { application = "orders" environment = "demo" } expect_failures = [var.environment] }

Given the expected failure is named, an unrelated broken resource still fails the test even if the environment validation no longer runs.

Assertions work best when they cover the module interface, including resource arguments derived from inputs, validation rules, outputs, and relationships between resources. Repeating every literal from main.tf tends to produce tests that change whenever the implementation is rearranged, even when callers see no difference.

Choose between mocked and real providers

Available in Terraform 1.7 and later, mocked providers preserve the provider schema and synthesize computed values so module logic can be tested without credentials or slow cloud resources. When an assertion relies on a computed value, the mock can supply a stable result. By default, that value is available during apply, so use a mocked apply run for the assertion:

mock_provider "aws" { mock_resource "aws_s3_bucket" { defaults = { arn = "arn:aws:s3:::orders-development-receipts" } } } run "receipt_bucket_arn" { command = apply variables { application = "orders" environment = "development" } assert { condition = aws_s3_bucket.receipts.arn == "arn:aws:s3:::orders-development-receipts" error_message = "The bucket ARN does not match the mocked value" } }

Use this as a separate test file rather than adding a second default mock_provider "aws" block to the first file. The provider is mocked, so this apply creates test state without calling AWS. A plan assertion on a computed value instead needs plan-time mock overrides, supported by the Terraform version you use.

Proving that AWS accepts the configuration, IAM permits the operation, and several managed services behave correctly together requires a real provider, usually in an isolated account with unique names, narrow credentials, explicit time and cost limits, and no route to production data.

Terraform attempts to destroy infrastructure created by an apply run after the tests finish, including when a later assertion fails. Cleanup can still fail because of provider errors, dependencies, or external changes, so CI needs to retain the test output and make abandoned resources visible rather than assuming every failed job left nothing behind.

Run Terraform tests in CI

A module pipeline normally keeps fast, credential-free tests separate from slower integration runs:

terraform fmt -check -recursive terraform init -backend=false terraform validate terraform test -filter=tests/bucket.tftest.hcl

The -filter option limits the run to selected test files, while -test-directory changes the default tests/ location. -verbose prints the plan or state produced by each run block and is useful when an assertion fails; -json provides machine-readable output for CI integrations.

Mocked plan tests can run on each pull request without cloud credentials. Real-provider tests need the same controls as any other deployment, including trusted code, short-lived credentials, an isolated account or project, concurrency limits, and cleanup monitoring. Keeping those two jobs distinct also makes failures easier to interpret, since a module assertion and an unavailable cloud API are different problems.

Testing application infrastructure with Encore

Encore

Terraform tests fit shared networking, IAM, and other resources Terraform owns, while application infrastructure needs another layer of testing because a valid plan cannot show whether the application uses the resource correctly.

By declaring databases, queues, and buckets in the application code with Encore, tests can start local versions of those resources, write a receipt through the API, and verify the complete workflow without a Terraform plan or cloud account. Encore then uses the same declarations to provision the supported resources when the application is deployed (to your cloud account).

Terraform can continue to own and test the shared cloud foundations around the application without retaining a separate module and test suite for the application resources Encore manages.

Learn how Encore works →

Frequently asked questions

What does terraform test do?

The terraform test command finds .tftest.hcl or .tftest.json files, runs the plan or apply operations defined in their run blocks, and evaluates assertions against the resulting plan or state.

Does terraform test create real infrastructure?

It can. A run block uses apply by default, and a real provider can create billable resources. Set command = plan when a plan is sufficient, or use a mock_provider block when the test does not need to call the provider API.

What is the difference between terraform validate and terraform test?

terraform validate checks whether a configuration is syntactically valid and internally consistent. terraform test executes plan or apply runs and lets you assert that variables, resources, and outputs behave as intended.

Which Terraform version supports mocked providers?

Provider mocking in terraform test requires Terraform 1.7 or newer. The test command itself was introduced earlier, but projects using mock_provider should set required_version accordingly.

Where should Terraform test files go?

Terraform discovers test files in the configuration directory and, by default, the tests directory beneath it. Test files use the .tftest.hcl or .tftest.json extension.

How can I test application infrastructure locally?

Encore starts local databases, Pub/Sub, and object storage from the resources declared in your application code. Tests can then exercise API requests, database queries, and message handling together without provisioning cloud resources for each run.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders