What this command does in detail
Terraform's native test framework looks for *.tftest.hcl files (by default in a tests/ subfolder) and runs each run block inside them in order. Each run block behaves like a miniature plan or apply against the configuration, then checks one or more assert conditions against the result — if a condition is false, that run fails and prints its error_message.
terraform/tests/infrastructure.tftest.hcl# Uses a mock provider: even the apply below never creates Azure resources.
mock_provider "azurerm" {
mock_resource "azurerm_postgresql_flexible_server" {
defaults = {
id = "/subscriptions/.../flexibleServers/test-pg"
fqdn = "test-pg.postgres.database.azure.com"
}
}
mock_resource "azurerm_container_registry" {
defaults = {
id = "/subscriptions/.../registries/test-acr"
login_server = "testacr.azurecr.io"
}
}
# ...more mocked resource types
}
mock_provider stands in for the real azurerm provider, returning fake but realistic values (like a made-up server ID and hostname) instead of calling Azure. Even a test run block using command = apply is completely safe — it “applies” only against this fake provider.
Where to run it from working directory
terraform -chdir=terraform test terraform -chdir=terraform/bootstrap test
Run separately for each root module — this project has test files for both the main configuration and bootstrap/main.tf.
Precondition what must already be true
- terraform init has run in this folder — -backend=false is enough, since tests use the mock provider and never touch real state.
- Test files exist under tests/ (Terraform's default location) using the .tftest.hcl extension.
- Each test file supplies its own variables block with values for anything the configuration requires — a test doesn't read your real terraform.tfvars or TF_VAR_* environment variables; it needs its own.
Postcondition what becomes true after it succeeds
- No real Azure resources or real state are affected at all — everything ran against the mock provider.
- Each run block is reported as passed or failed, with a summary at the end.
- A passing test suite means the configuration's structure and computed values match the assumptions the tests encode — it does not confirm Azure will actually accept those values (name availability, quota, and live permissions are still only proven by a real plan/apply).
How this project uses it real examples
run "matches_powershell_resources" {
command = apply
assert {
condition = azurerm_postgresql_flexible_server.metals.sku_name == "B_Standard_B1ms"
&& azurerm_postgresql_flexible_server.metals.storage_mb == 32768
&& azurerm_postgresql_flexible_server.metals.version == "16"
error_message = "Database sizing and version must match the PowerShell demo."
}
assert {
condition = azurerm_linux_web_app.api.site_config[0].application_stack[0].docker_image_name == "metals-api:latest"
&& azurerm_linux_web_app.api.app_settings["WEBSITES_PORT"] == "5000"
error_message = "The API app must run the metals-api container on port 5000."
}
assert {
condition = azurerm_linux_web_app.ui.app_settings["API_UPSTREAM"] == "https://${azurerm_linux_web_app.api.default_hostname}"
error_message = "The UI app must proxy to the API app's actual hostname."
}
# ...more assertions, covering the firewall rule, OIDC subject, and role assignments
}
These assertions catch exactly the kind of mistake that's easy to introduce while editing database.tf, app_service.tf, or github_oidc.tf — a web app pointed at the wrong image or port, or the UI's API_UPSTREAM hardcoded to a hostname instead of referencing the API resource. Both are silent in a plan and obvious in a test.
terraform -chdir=terraform test terraform -chdir=terraform/bootstrap test ... python -m unittest discover -s terraform/tests -p 'test_*.py'
This project's CI validate job runs terraform test for both root modules, and separately runs a Python unit test suite (test_initialize_database.py) against terraform/scripts/initialize_database.py — two different test frameworks, for two different languages, in the same job.
Common errors and how to fix them
| What you see | Likely cause | How to fix it |
|---|---|---|
| “Error: Test assertion failed” | An assert condition evaluated to false — the actual computed value doesn't match what the test expects. | Read the error_message and the reported actual value; either fix the configuration, or update the test if the expected behavior genuinely changed on purpose. |
| “No value for required variable” inside a test | The test's own variables block doesn't supply every variable the configuration requires. | Add the missing variable to the variables block in that test file (or the run block, which can override it per-run). |
| A test tries to reach real Azure | A run block's provider wasn't actually overridden by mock_provider (for example, a missing providers mapping on the run block in a more complex test file). | Confirm the test file's mock_provider block is defined and wired up before any run block that uses command = apply. |
| “Could not load plugin” | Same root cause as every other command: terraform init hasn't run in this folder yet. | Run terraform init -backend=false first. |