qaware.de
Testing in Terraform
only for platform nerds?
Alexander Eimer
alexander.eimer@qaware.de
@aeimer
Cloud Native Night: 27.11.2025
Agenda
1. Why test IaC-code at all?
2. Tests in Terraform
3. Testing Terraform in the CI
4. Tests in other IaC tools
Agenda
1. Why test IaC-code at all?
2. Tests in Terraform
3. Testing Terraform in the CI
4. Tests in other IaC tools
Why test IaC-code at all?
QAware | 4
Fail fast Document assumptions
Validate compliance
Motivation / benefits are exactly the same as for tests of application code
As with application code, the cost-benefit ratio needs to be appropriate.
Agenda
1. Why test IaC-code at all?
2. Tests in Terraform
3. Testing Terraform in the CI
4. Tests in other IaC tools
Overview over the different types of testing
QAware | 6
1. Validations (of variables)
2. Pre- / Postconditions (on resources)
3. “Unit tests” - custom tests on terraform plan output
4. “Integration tests” - custom tests on temporary deployed resources
5. “End-to-end tests” - custom tests on the live state
}“Contract tests”
Disclaimer: This depicts the “normal” testing pyramid. In Terraform, the
order is different!
Source: https://www.hashicorp.com/de/blog/testing-hashicorp-terraform
1. Validations
QAware | 7
● Validate input given in variables
○ Part of the contract of a
module
● Evaluated on the start of every
plan / apply / test
○ Will fail the command and
prevent broken configuration
to be applied
● Defined for a specific variable,
but can contain any expressions
and can reference other variables
(terraform ~> v1.10)
● Multiple blocks per variable are
possible (all conditions have to be
satisfied)
variable "bucket_name" {
description = "The name of the S3 bucket."
type = string
validation {
condition = (
length(var.bucket_name) >= 3 && length(var.bucket_name) <= 63
)
error_message = "Invalid bucket name. The name must be 3 to 63 characters long."
}
}
2a. Preconditions
QAware | 8
● Document assumptions
○ Define contracts between
resources within a module
● Evaluated on every plan / apply /
test (before apply)
○ Will fail the command and
prevent broken configuration
to be applied
● Defined for a specific resource or
output, can reference all objects
available before apply (including
data sources)
Source: https://developer.hashicorp.com/terraform/language/expressions/custom-conditions#preconditions-and-postconditions
data "aws_ami" "example" {
...
}
resource "aws_instance" "example" {
instance_type = "t3.micro"
ami = data.aws_ami.example.id
lifecycle {
# The AMI ID must refer to an AMI that contains an operating system
# for the `x86_64` architecture.
precondition {
condition = data.aws_ami.example.architecture == "x86_64"
error_message = "The selected AMI must be for the x86_64 architecture."
}
}
}
2b. Postconditions
QAware | 9
● Codify guarantees
● Evaluated on every plan / apply /
test (“asap”)
○ Will fail the command and
stop downstream processing
(but will not undo any
configuration already
applied).
● Defined for a specific resource or
output, can reference pretty
much everything, including itself
resource "aws_s3_object" "example_object" {
bucket = ver.bucket_name
key = "example_object"
source = "test.txt"
lifecycle {
postcondition {
condition = try(self.version_id != "null", false)
error_message = "The example object must be versioned."
}
}
}
3. “Unit Tests” – terraform test on plan
QAware | 10
● Custom tests on the plan-output
● Only evaluated when explicitly
running terraform test
○ No infrastructure changes!
○ But real data sources read
● Defined for an entire module
● Defined in a separate file ending
in .tftest.hcl
● Provider behaviour can be
mocked (e.g. simulate data
source giving a specific value)
# main.tf
…
resource "aws_s3_bucket" "bucket" {
bucket = "${var.bucket_prefix}-bucket"
}
# valid_string_concat.tftest.hcl
variables {
bucket_prefix = "test"
}
run "valid_string_concat" {
command = plan
assert {
condition = aws_s3_bucket.bucket.bucket == "test-bucket"
error_message = "S3 bucket name did not match expected"
}
}
Source: https://developer.hashicorp.com/terraform/language/tests
4. “Integration Tests” – terraform test on apply
QAware | 11
● Custom tests with real
infrastructure
● Only evaluated when explicitly
running terraform test
○ Real infrastructure created
and then torn down
● Defined for an entire module
● Defined in a separate file ending in
.tftest.hcl
● Provider behaviour can be
mocked (e.g. simulate some
resource already present)
# main.tf
…
resource "aws_s3_bucket" "bucket" {
bucket = "${var.bucket_prefix}-bucket"
}
# valid_string_concat.tftest.hcl
variables {
bucket_prefix = "test"
}
run "valid_string_concat" {
command = apply
assert {
condition = aws_s3_bucket.bucket.bucket == "test-bucket"
error_message = "S3 bucket name did not match expected"
}
}
Source: https://developer.hashicorp.com/terraform/language/tests
5. “End-to-end Tests” – advanced validation of the real apply
QAware | 12
● Validate infrastructure
automatically (but independent
of resource lifecycle)
● Evaluated at the end of every
plan / apply / test
○ Will not block the
command.
● Defined for an entire module
check "website_status_code" {
data "http" "static_website" {
url = local.website_endpoint
}
assert {
condition = data.http.static_website.status_code == 200
error_message = "${data.http.static_website.url} returned an unhealthy status"
}
}
Demo Repo
QAware | 13
https://github.com/qaware/state-of-terraform-2025
To the audience:
Who already used terraform tests?
Agenda
1. Why test IaC-code at all?
2. Tests in Terraform
3. Testing Terraform in the CI
4. Tests in other IaC tools
Which tests make sense to run regularly?
● Checked by default with each terraform apply
○ Validations
○ Preconditions
○ Postconditions
○ “End-to-End”-Tests
● Needs to be checked manually by terraform test
○ “Unit”-Tests
○ “Integration”-Tests
● Needs manual intervention
○ Manual Tests
QAware | 16
From IDP-project experience
QAware | 17
When building platforms (IDPs) only running terraform internal testing usually is not sufficient.
● Glue code needs to be tested
○ It’s not only terraform, but also the pipelines etc
● External interfaces need to be tested
○ IDP(-management) interacts with other systems, that needs to be tested
● Advanced tests to verify functionality of deployed infrastructure
○ Testing a real world workload
⇒ A custom End-to-End testing framework needs to be built for advanced use-cases
Agenda
1. Why test IaC-code at all?
2. Tests in Terraform
3. Testing Terraform in the CI
4. Tests in other IaC tools
Tests in OpenTofu
QAware | 19
… basically the same as in terraform.
● Validation, custom conditions and check blocks were already included in Terraform 1.5 and became
part of OpenTofu with the fork.
● terraform test was only introduced in Terraform 1.6, but was included in OpenTofu 1.6.
● Provider mocking was included in 1.8.
● OpenTofu has not yet introduced any (major) features of its own in this area.
● Looking at the details, there are of course some initial minor deviations.
External Terraform Test-Frameworks
⇒ Terratest, Kitchen-Terraform
QAware | 20
Now obsolete for most test cases.
Often created before tests were introduced in Terraform in their current form.
● Kitchen Terraform has since been deprecated.
Disadvantage:
● Additional tool
● Often different syntax or programming language (e.g. Go or Ruby)
However, it can be useful in special cases, e.g.
● Very complex test cases that require (a lot of) custom test code
● Testing multiple tools with the same test tool (e.g. Terratest for Terraform & Packer & Kubernetes)
Tests in Pulumi
QAware | 21
Pulumi uses ‘normal’ programming languages
⇒ We can use ‘normal’ test frameworks for these languages directly.
Concepts similar to the test pyramid, see https://www.pulumi.com/docs/iac/concepts/testing/
Tests in Crossplane
QAware | 22
Based on Kubernetes manifests and operators, therefore harder to test.
Possible solutions: (I didn’t look into)
● KUTTL ⇒ Blog article
● Crossplane Uptest ⇒ GitHub
Honorable mentions
QAware | 23
● terraform validate ⇒ static analysis
● terraform fmt ⇒ formatting
● checkov ⇒ Policy-as-Code
● tflint ⇒ static analysis
● trivy ⇒ static analysis
QAware | 24
tl;dr
Better test once, instead of retrying multiple times!
Q&A
QAware | 26
https://speakerdeck.com/aeimer
qaware.de
QAware GmbH Mainz
Rheinstraße 4 C
55116 Mainz
Tel. +49 6131 21569-0
info@qaware.de
twitter.com/qaware
linkedin.com/company/qaware-gmbh
xing.com/companies/qawaregmbh
slideshare.net/qaware
github.com/qaware

Testing in Terraform: only for platform nerds?

  • 1.
    qaware.de Testing in Terraform onlyfor platform nerds? Alexander Eimer alexander.eimer@qaware.de @aeimer Cloud Native Night: 27.11.2025
  • 2.
    Agenda 1. Why testIaC-code at all? 2. Tests in Terraform 3. Testing Terraform in the CI 4. Tests in other IaC tools
  • 3.
    Agenda 1. Why testIaC-code at all? 2. Tests in Terraform 3. Testing Terraform in the CI 4. Tests in other IaC tools
  • 4.
    Why test IaC-codeat all? QAware | 4 Fail fast Document assumptions Validate compliance Motivation / benefits are exactly the same as for tests of application code As with application code, the cost-benefit ratio needs to be appropriate.
  • 5.
    Agenda 1. Why testIaC-code at all? 2. Tests in Terraform 3. Testing Terraform in the CI 4. Tests in other IaC tools
  • 6.
    Overview over thedifferent types of testing QAware | 6 1. Validations (of variables) 2. Pre- / Postconditions (on resources) 3. “Unit tests” - custom tests on terraform plan output 4. “Integration tests” - custom tests on temporary deployed resources 5. “End-to-end tests” - custom tests on the live state }“Contract tests” Disclaimer: This depicts the “normal” testing pyramid. In Terraform, the order is different! Source: https://www.hashicorp.com/de/blog/testing-hashicorp-terraform
  • 7.
    1. Validations QAware |7 ● Validate input given in variables ○ Part of the contract of a module ● Evaluated on the start of every plan / apply / test ○ Will fail the command and prevent broken configuration to be applied ● Defined for a specific variable, but can contain any expressions and can reference other variables (terraform ~> v1.10) ● Multiple blocks per variable are possible (all conditions have to be satisfied) variable "bucket_name" { description = "The name of the S3 bucket." type = string validation { condition = ( length(var.bucket_name) >= 3 && length(var.bucket_name) <= 63 ) error_message = "Invalid bucket name. The name must be 3 to 63 characters long." } }
  • 8.
    2a. Preconditions QAware |8 ● Document assumptions ○ Define contracts between resources within a module ● Evaluated on every plan / apply / test (before apply) ○ Will fail the command and prevent broken configuration to be applied ● Defined for a specific resource or output, can reference all objects available before apply (including data sources) Source: https://developer.hashicorp.com/terraform/language/expressions/custom-conditions#preconditions-and-postconditions data "aws_ami" "example" { ... } resource "aws_instance" "example" { instance_type = "t3.micro" ami = data.aws_ami.example.id lifecycle { # The AMI ID must refer to an AMI that contains an operating system # for the `x86_64` architecture. precondition { condition = data.aws_ami.example.architecture == "x86_64" error_message = "The selected AMI must be for the x86_64 architecture." } } }
  • 9.
    2b. Postconditions QAware |9 ● Codify guarantees ● Evaluated on every plan / apply / test (“asap”) ○ Will fail the command and stop downstream processing (but will not undo any configuration already applied). ● Defined for a specific resource or output, can reference pretty much everything, including itself resource "aws_s3_object" "example_object" { bucket = ver.bucket_name key = "example_object" source = "test.txt" lifecycle { postcondition { condition = try(self.version_id != "null", false) error_message = "The example object must be versioned." } } }
  • 10.
    3. “Unit Tests”– terraform test on plan QAware | 10 ● Custom tests on the plan-output ● Only evaluated when explicitly running terraform test ○ No infrastructure changes! ○ But real data sources read ● Defined for an entire module ● Defined in a separate file ending in .tftest.hcl ● Provider behaviour can be mocked (e.g. simulate data source giving a specific value) # main.tf … resource "aws_s3_bucket" "bucket" { bucket = "${var.bucket_prefix}-bucket" } # valid_string_concat.tftest.hcl variables { bucket_prefix = "test" } run "valid_string_concat" { command = plan assert { condition = aws_s3_bucket.bucket.bucket == "test-bucket" error_message = "S3 bucket name did not match expected" } } Source: https://developer.hashicorp.com/terraform/language/tests
  • 11.
    4. “Integration Tests”– terraform test on apply QAware | 11 ● Custom tests with real infrastructure ● Only evaluated when explicitly running terraform test ○ Real infrastructure created and then torn down ● Defined for an entire module ● Defined in a separate file ending in .tftest.hcl ● Provider behaviour can be mocked (e.g. simulate some resource already present) # main.tf … resource "aws_s3_bucket" "bucket" { bucket = "${var.bucket_prefix}-bucket" } # valid_string_concat.tftest.hcl variables { bucket_prefix = "test" } run "valid_string_concat" { command = apply assert { condition = aws_s3_bucket.bucket.bucket == "test-bucket" error_message = "S3 bucket name did not match expected" } } Source: https://developer.hashicorp.com/terraform/language/tests
  • 12.
    5. “End-to-end Tests”– advanced validation of the real apply QAware | 12 ● Validate infrastructure automatically (but independent of resource lifecycle) ● Evaluated at the end of every plan / apply / test ○ Will not block the command. ● Defined for an entire module check "website_status_code" { data "http" "static_website" { url = local.website_endpoint } assert { condition = data.http.static_website.status_code == 200 error_message = "${data.http.static_website.url} returned an unhealthy status" } }
  • 13.
    Demo Repo QAware |13 https://github.com/qaware/state-of-terraform-2025
  • 14.
    To the audience: Whoalready used terraform tests?
  • 15.
    Agenda 1. Why testIaC-code at all? 2. Tests in Terraform 3. Testing Terraform in the CI 4. Tests in other IaC tools
  • 16.
    Which tests makesense to run regularly? ● Checked by default with each terraform apply ○ Validations ○ Preconditions ○ Postconditions ○ “End-to-End”-Tests ● Needs to be checked manually by terraform test ○ “Unit”-Tests ○ “Integration”-Tests ● Needs manual intervention ○ Manual Tests QAware | 16
  • 17.
    From IDP-project experience QAware| 17 When building platforms (IDPs) only running terraform internal testing usually is not sufficient. ● Glue code needs to be tested ○ It’s not only terraform, but also the pipelines etc ● External interfaces need to be tested ○ IDP(-management) interacts with other systems, that needs to be tested ● Advanced tests to verify functionality of deployed infrastructure ○ Testing a real world workload ⇒ A custom End-to-End testing framework needs to be built for advanced use-cases
  • 18.
    Agenda 1. Why testIaC-code at all? 2. Tests in Terraform 3. Testing Terraform in the CI 4. Tests in other IaC tools
  • 19.
    Tests in OpenTofu QAware| 19 … basically the same as in terraform. ● Validation, custom conditions and check blocks were already included in Terraform 1.5 and became part of OpenTofu with the fork. ● terraform test was only introduced in Terraform 1.6, but was included in OpenTofu 1.6. ● Provider mocking was included in 1.8. ● OpenTofu has not yet introduced any (major) features of its own in this area. ● Looking at the details, there are of course some initial minor deviations.
  • 20.
    External Terraform Test-Frameworks ⇒Terratest, Kitchen-Terraform QAware | 20 Now obsolete for most test cases. Often created before tests were introduced in Terraform in their current form. ● Kitchen Terraform has since been deprecated. Disadvantage: ● Additional tool ● Often different syntax or programming language (e.g. Go or Ruby) However, it can be useful in special cases, e.g. ● Very complex test cases that require (a lot of) custom test code ● Testing multiple tools with the same test tool (e.g. Terratest for Terraform & Packer & Kubernetes)
  • 21.
    Tests in Pulumi QAware| 21 Pulumi uses ‘normal’ programming languages ⇒ We can use ‘normal’ test frameworks for these languages directly. Concepts similar to the test pyramid, see https://www.pulumi.com/docs/iac/concepts/testing/
  • 22.
    Tests in Crossplane QAware| 22 Based on Kubernetes manifests and operators, therefore harder to test. Possible solutions: (I didn’t look into) ● KUTTL ⇒ Blog article ● Crossplane Uptest ⇒ GitHub
  • 23.
    Honorable mentions QAware |23 ● terraform validate ⇒ static analysis ● terraform fmt ⇒ formatting ● checkov ⇒ Policy-as-Code ● tflint ⇒ static analysis ● trivy ⇒ static analysis
  • 24.
    QAware | 24 tl;dr Bettertest once, instead of retrying multiple times!
  • 25.
  • 26.
  • 27.
    qaware.de QAware GmbH Mainz Rheinstraße4 C 55116 Mainz Tel. +49 6131 21569-0 info@qaware.de twitter.com/qaware linkedin.com/company/qaware-gmbh xing.com/companies/qawaregmbh slideshare.net/qaware github.com/qaware