Writing a Test From Scratch¶
In this section, we'll write a simple Terraform code to create some local resources. We'll then write a configuration to run tests using infra-tester.
Before proceeding any further, make sure Terraform and infra-tester are installed in your environment. Refer to Install infra-tester section on how to install it.
Terraform¶
Since we'd like this example to be simple and easy to follow, we'll
use the time
provider to create a time "resource" and then we'll see how we can test it.
Let's start by creating a Terraform file and then adding the terraform block with version constraints and required providers.
test.tf | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
Now let's define a basic time_static
resource and then add its value to the
outputs.
test.tf | |
---|---|
10 11 12 13 14 15 16 |
|
And that's it for the Terraform code. Let's try it out.
terraform init
terraform apply # Review your plan and approve the changes.
# You should see the `current_time` output. You can also run
terraform show # to see the outputs.
terraform destroy # To destroy the resources.
Writing Tests Using infra-tester¶
Let's imagine that we use this Terraform code or module to generate a time stamp which is then consumed by other modules. Maybe the other modules expect
the output to be in a certain format. In this case, we'd like to ensure that
the output adheres to the RFC 3339 format no matter what underlying provider
we use to generate the current_time
output.
A basic test would be a regular expression matching to make sure the output adheres to the RFC 3339 format. Let's see how we can write such a test using infra-tester.
Let's create a .infra-tester-config.yaml
file in the same directory where we created the
Terraform file and copy the below code into it. See the annotation next
to the code to understand what it does.
.infra-tester-config.yaml | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
- This is the test plan name. All the tests are grouped under this test plan name. It's best to name it the same as the module or component name.
- The
tests
block can contain a list of tests that are to be run. - Each test must have a unique name. This name will show up in the final test output.
- The
apply
block can contain assertions that will be run afterterraform apply
was run. Similarly, there's also aplan
block which can also contain a list of assertions that are to be run after aterraform plan
. - The
assertions
block can contain a list of assertions to be run under theplan
orapply
step depending on whether it's defined underplan
orapply
block. - An assertion can optionally have a name. If a name is not provided the
type
of assertion will be used to generate a name for the assertion in the test output. type
of assertion determines what assertion will be run. This must be a valid assertion. infra-tester provides several inbuilt assertion types. It also supports a plugin system to introduce custom assertions as well.output_name
is an input field specific to theOutputMatchesRegex
assertion. The assertion captures the string value of the output namedoutput_name
to match the regular expression.OutputMatchesRegex
specific input field which will be used to match against the output value.
The above configuration is all that's required to test the use case we mentioned before. Now let's run the tests using infra-tester.
Running the Tests¶
Change the working directory to the same directory where you created the Terraform
file and the .infra-tester-config.yaml
file and run
infra-tester -test.v
The -test.v
flag can be used to run tests in verbose mode.
You should see the logs appear as the test runs, and finally, the test output is printed.
--- PASS: Tests (3.35s)
--- PASS: Tests/Time (2.97s)
--- PASS: Tests/Time/CurrentTimeOutputTests (1.03s)
--- PASS: Tests/Time/CurrentTimeOutputTests/Apply (1.03s)
--- PASS: Tests/Time/CurrentTimeOutputTests/Apply/TimeStringMatchesRFC3339 (0.09s)
PASS
Try Breaking It!¶
Let's modify the regular expression so that it's invalid and see what happens.
To make it invalid let's remove the first two opening brackets, so the line would then be:
.infra-tester-config.yaml | |
---|---|
10 |
|
Let's run infra-tester -test.v
to see what happens.
$ infra-tester -test.v
=== RUN Tests
assertions.go:117: ERROR: Failure during test validation: test 'CurrentTimeOutputTests' failed validation: assertion 'OutputMatchesRegex' for apply step failed validation because - invalid regular expression
--- FAIL: Tests (0.36s)
FAIL
Catching issues early on is very important, especially in the case of Infrastructure as Code, as it reduces the time wasted on silly typos and easy-to-catch issues. This leads to a better developer experience.
Let us now try to pass it a valid regular expression but one that doesn't adhere to RFC 3339. Let's modify the line to the following:
.infra-tester-config.yaml | |
---|---|
10 |
|
And now if you run infra-tester again, the validation passes, but the test fails as expected:
--- FAIL: Tests (1.60s)
--- FAIL: Tests/Time (1.27s)
--- FAIL: Tests/Time/CurrentTimeOutputTests (0.47s)
--- FAIL: Tests/Time/CurrentTimeOutputTests/Apply (0.47s)
--- FAIL: Tests/Time/CurrentTimeOutputTests/Apply/TimeStringMatchesRFC3339 (0.08s)
FAIL
What's More?¶
This section covered the basics of infra-tester. There are several more features like passing variable inputs through the YAML configuration, partially matching complex Terraform outputs, creating custom assertions to extend infra-tester's capabilities and so on, all of which are extensively documented in this documentation site.