Terraform
Command: test
The terraform test
command reads in Terraform testing files and executes the tests within.
The test
command, and the test file syntax, are particularly helpful for module authors who want to validate and test their shared modules. You can also use the test
command to validate root modules.
Usage
Usage: terraform test [options]
This command searches the current directory and the specified testing directory (tests
, by default) for any Terraform testing files, and executes the specified tests. Refer to Tests for more details on test files.
Terraform then executes a series of Terraform plan or apply commands according to the test files' specifications, and also validates the relevant plan and state files according to the test files' specifications.
Warning: The Terraform test command can create real infrastructure than can cost you money. Refer to the Terraform Test Cleanup section for best practices on ensuring created infrastructure is destroyed.
General Options
The following options apply to the Terraform test
command:
-cloud-run=<module source>
- This test run executes remotely on HCP Terraform within the specified Terraform private registry module.-filter=testfile
- Limits theterraform test
operation to the specified test files.-json
- Displays machine-readable JSON output for your testing results.-junit-xml=<output file path>
- Saves testing results output in JUnit XML format to the specified file. The file path must be relative or absolute.-test-directory=<relative directory>
- Overrides the directory that Terraform looks into for test files. Note that Terraform always loads testing files within the main configuration directory. The default testing directory istests
.-verbose
- Prints out the plan or state for eachrun
block within a test file, based on thecommand
attribute of eachrun
block.
State Management
Each Terraform test file will maintain all Terraform state it requires within memory as it executes, starting empty. This state is entirely separate from any existing state for the configuration under test, so you can safely execute Terraform test commands without affecting any live infrastructure.
Terraform Test Cleanup
The Terraform test
command creates real infrastructure. Once Terraform fully executes each test file, Terraform attempts to destroy any remaining infrastructure. If it cannot do this, Terraform reports a list of resources it created but could not destroy.
You should monitor the output of the test command closely to ensure Terraform removes the infrastructure it created or perform manual cleanup if not. We recommend creating dedicated testing accounts within the target providers that you can routinely and safely purge to ensure any accidental and costly resources aren't left behind.
Terraform also provides diagnostics explaining why it could not automatically clean up. You should review these diagnostics to ensure that future clean-up operations are successful.
HCP Terraform execution
You can execute tests remotely on HCP Terraform using the -cloud-run
option.
The -cloud-run
option accepts a private registry module source. This option associates the test run with your specified private module within the HCP Terraform user interface.
You must provide a module from a private registry, not the public Terraform registry.
You must execute terraform login
before using this option, and ensure that your hostname
argument matches the private registry hostname of your target module.
Example: Test Directory Structure and Commands
The following directory structure represents an example directory tree for a Terraform module with tests and a setup module.
project/
|-- main.tf
|-- outputs.tf
|-- terraform.tf
|-- variables.tf
|-- tests/
| |-- validations.tftest.hcl
| |-- outputs.tftest.hcl
|-- testing/
|-- setup/
|-- main.tf
|-- outputs.tf
|-- terraform.tf
|-- variables.tf
At the root directory of the project, there are some typical Terraform configuration files: main.tf
, outputs.tf
, terraform.tf
, and variables.tf
. The test files, validations.tftest.hcl
and outputs.tftest.hcl
, are within the default tests directory: tests
.
In addition, a setup module for the tests exists within the testing
directory.
In order to execute the tests you should run terraform test
from the root configuration directory as if running terraform plan
or terraform apply
. Despite the actual test files being in the nested tests
directory, Terraform executes from the main configuration directory.
Specific test files can be executed using the -filter
option.
Linux, Mac OS, and UNIX:
terraform test -filter=tests/validations.tftest.hcl
PowerShell:
terraform test -filter='tests\validations.tftest.hcl'
Windows cmd.exe
:
terraform test -filter=tests\validations.tftest.hcl
Alternate Test Directories
In the above example the tests are in the default testing directory of tests
. Test files can also be included directly within the main configuration directory:
project/
|-- main.tf
|-- outputs.tf
|-- terraform.tf
|-- variables.tf
|-- validations.tftest.hcl
|-- outputs.tftest.hcl
|-- testing/
|-- setup/
|-- main.tf
|-- outputs.tf
|-- terraform.tf
|-- variables.tf
The location of the testing files does not affect the operation of terraform test
. All references to, and absolute file paths within, the testing files should be relative to the main configuration directory.
You can also use the -test-directory
argument to change the location of the testing files. For example, terraform test -test-directory=testing
would instruct Terraform to load tests from the directory testing
instead of tests
.
The testing directory must be beneath the main configuration directory, but it can be nested many times.
Note: Test files within the root configuration directory are always loaded, regardless of the
-test-directory
value.
We do not recommend changing the default test directory. The option for customization is included for configuration authors who may have included a tests
submodule in their configuration before the terraform test
command was released. In general, the default test directory of tests
should always be used.
Example: Test Output Format Options
Below is a contrived example of Terraform testing that makes assertions about the values of local variables true
and false
.
There are two test files: one contains a passing test, and one contains a failing test.
# main.tf
locals {
true = "true"
false = "true" # incorrect, should be "false"!
}
The assertion that local.true == "true" in example_1.tftest.hcl will pass:
# example_1.tftest.hcl
run "true_is_true" {
assert {
condition = local.true == "true"
error_message = "local.true did not match expected value"
}
}
The assertion that local.false == "false" in example_2.tftest.hcl will fail:
# example_2.tftest.hcl
run "false_is_false" {
assert {
condition = local.false == "false"
error_message = "local.false did not match expected value"
}
}
General features of test output
The test
command will print output to the terminal:
- To show progress running tests across all files.
- To display any error messages resulting from failing test assertions.
- Finally, to summarize the results across all files.
Below is the output of terraform test
when testing the example files above. When no additional flags are supplied, the command's output defaults to a human-readable format:
example_1.tftest.hcl... in progress
run "true_is_true"... pass
example_1.tftest.hcl... tearing down
example_1.tftest.hcl... pass
example_2.tftest.hcl... in progress
run "false_is_false"... fail
╷
│ Error: Test assertion failed
│
│ on example_2.tftest.hcl line 21, in run "false_is_false":
│ 21: condition = local.false == "false"
│ ├────────────────
│ │ local.false is "true"
│
│ local.false did not match expected value
╵
example_2.tftest.hcl... tearing down
example_2.tftest.hcl... fail
Failure! 1 passed, 1 failed.
Test output in JSON format
Below is the output of the terraform test -json
command using the same example files. The -json flag changes terminal output to be in a machine-readable format. The content of the output is otherwise unchanged:
{"@level":"info","@message":"Terraform 1.11.0-dev","@module":"terraform.ui","@timestamp":"2025-01-06T12:19:01.903603Z","terraform":"1.11.0-dev","type":"version","ui":"1.2"}
{"@level":"info","@message":"Found 2 files and 2 run blocks","@module":"terraform.ui","@timestamp":"2025-01-06T12:19:02.229367Z","test_abstract":{"example_1.tftest.hcl":["true_is_true"],"example_2.tftest.hcl":["false_is_false"]},"type":"test_abstract"}
{"@level":"info","@message":"example_1.tftest.hcl... in progress","@module":"terraform.ui","@testfile":"example_1.tftest.hcl","@timestamp":"2025-01-06T12:19:02.229432Z","test_file":{"path":"example_1.tftest.hcl","progress":"starting"},"type":"test_file"}
{"@level":"info","@message":" \"true_is_true\"... in progress","@module":"terraform.ui","@testfile":"example_1.tftest.hcl","@testrun":"true_is_true","@timestamp":"2025-01-06T12:19:02.229458Z","test_run":{"path":"example_1.tftest.hcl","run":"true_is_true","progress":"starting","elapsed":0},"type":"test_run"}
{"@level":"info","@message":" \"true_is_true\"... pass","@module":"terraform.ui","@testfile":"example_1.tftest.hcl","@testrun":"true_is_true","@timestamp":"2025-01-06T12:19:02.230577Z","test_run":{"path":"example_1.tftest.hcl","run":"true_is_true","progress":"complete","status":"pass"},"type":"test_run"}
{"@level":"info","@message":"example_1.tftest.hcl... tearing down","@module":"terraform.ui","@testfile":"example_1.tftest.hcl","@timestamp":"2025-01-06T12:19:02.230590Z","test_file":{"path":"example_1.tftest.hcl","progress":"teardown"},"type":"test_file"}
{"@level":"info","@message":"example_1.tftest.hcl... pass","@module":"terraform.ui","@testfile":"example_1.tftest.hcl","@timestamp":"2025-01-06T12:19:02.230598Z","test_file":{"path":"example_1.tftest.hcl","progress":"complete","status":"pass"},"type":"test_file"}
{"@level":"info","@message":"example_2.tftest.hcl... in progress","@module":"terraform.ui","@testfile":"example_2.tftest.hcl","@timestamp":"2025-01-06T12:19:02.230605Z","test_file":{"path":"example_2.tftest.hcl","progress":"starting"},"type":"test_file"}
{"@level":"info","@message":" \"false_is_false\"... in progress","@module":"terraform.ui","@testfile":"example_2.tftest.hcl","@testrun":"false_is_false","@timestamp":"2025-01-06T12:19:02.230642Z","test_run":{"path":"example_2.tftest.hcl","run":"false_is_false","progress":"starting","elapsed":0},"type":"test_run"}
{"@level":"info","@message":" \"false_is_false\"... fail","@module":"terraform.ui","@testfile":"example_2.tftest.hcl","@testrun":"false_is_false","@timestamp":"2025-01-06T12:19:02.231331Z","test_run":{"path":"example_2.tftest.hcl","run":"false_is_false","progress":"complete","status":"fail"},"type":"test_run"}
{"@level":"error","@message":"Error: Test assertion failed","@module":"terraform.ui","@testfile":"example_2.tftest.hcl","@testrun":"false_is_false","@timestamp":"2025-01-06T12:19:02.231485Z","diagnostic":{"severity":"error","summary":"Test assertion failed","detail":"local.false did not match expected value","range":{"filename":"example_2.tftest.hcl","start":{"line":21,"column":21,"byte":334},"end":{"line":21,"column":43,"byte":356}},"snippet":{"context":"run \"false_is_false\"","code":" condition = local.false == \"false\"","start_line":21,"highlight_start_offset":20,"highlight_end_offset":42,"values":[{"traversal":"local.false","statement":"is \"true\""}]}},"type":"diagnostic"}
{"@level":"info","@message":"example_2.tftest.hcl... tearing down","@module":"terraform.ui","@testfile":"example_2.tftest.hcl","@timestamp":"2025-01-06T12:19:02.231552Z","test_file":{"path":"example_2.tftest.hcl","progress":"teardown"},"type":"test_file"}
{"@level":"info","@message":"example_2.tftest.hcl... fail","@module":"terraform.ui","@testfile":"example_2.tftest.hcl","@timestamp":"2025-01-06T12:19:02.231557Z","test_file":{"path":"example_2.tftest.hcl","progress":"complete","status":"fail"},"type":"test_file"}
{"@level":"info","@message":"Failure! 1 passed, 1 failed.","@module":"terraform.ui","@timestamp":"2025-01-06T12:19:02.231581Z","test_summary":{"status":"fail","passed":1,"failed":1,"errored":0,"skipped":0},"type":"test_summary"}
Test output in JUnit XML format, saved to file
Below is the output of the terraform test -junit-xml=./output.xml
command using the same example files.
The test output is:
- Printed to the terminal in the default, human-readable format, as described above.
- Also saved in JUnit XML format in the specified file.
Below is the contents of the resulting output.xml
file:
<?xml version="1.0" encoding="UTF-8"?><testsuites>
<testsuite name="example_1.tftest.hcl" tests="1" skipped="0" failures="0" errors="0">
<testcase name="true_is_true" classname="example_1.tftest.hcl" time="0.001064666"></testcase>
</testsuite>
<testsuite name="example_2.tftest.hcl" tests="1" skipped="0" failures="1" errors="0">
<testcase name="false_is_false" classname="example_2.tftest.hcl" time="0.000870083">
<failure message="Test run failed"></failure>
<system-err><![CDATA[
Error: Test assertion failed
on example_2.tftest.hcl line 21:
(source code not available)
local.false did not match expected value
]]></system-err>
</testcase>
</testsuite>
</testsuites>
The file maps Terraform test command concepts to JUnit XML format according to the table below:
Terraform test concept | Element in JUnit XML output |
---|---|
Test directory | <testsuites> |
Run block | <testcase> |
Test failure error | <failure> |
??? | <system-err> |
A JUnit format XML file will contain a single <testsuites>
element that wraps all other elements in the XML output, as terraform test
only processes a single test directory. Inside <testsuites>
there will be as many <testsuite>
elements as there are test files. Test files can contain many run blocks, so <testsuite>
elements will contain a <testcase>
element per run block within that test file. If a run block's assertions fail then that <testcase>
element will contain a <failure>
element describing the test failure. Any other issues that arise while executing that run block will be reported in a similar <system-err>
element.
Some elements have attributes that provide addition information about the testing output data. These are summarized below:
JUnit XML Element | Attribute | Meaning |
---|---|---|
<testsuite> | name | The test file name |
<testsuite> | tests | Count of total tests (run blocks) in the file |
<testsuite> | skipped | Count of skipped tests (run blocs) count in the file |
<testsuite> | failures | Count of failed tests (run blocks) count in the file |
<testsuite> | errored | Count of errored tests (run blocks) count in the file |
<testcase> | name | The run block's name |
<testcase> | classname | The name of the file containing the run block |
<testcase> | time | The duration of executing the run block |
<failure> | message | A message describing the test failure |