> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ubiquex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Native Test Blocks

> Write plan-time assertions that verify your .iac compiles to the expected resource configuration.

Infrastructure bugs are expensive to find in production and cheap to find at compile time. A bucket that should be named `staging-assets` but gets named `prod-assets` because an input default is wrong. A database identifier that doesn't match what the application expects. A reference to a resource label that was renamed six months ago. These are the kinds of mistakes that break deployments, wake up on-call engineers, and take hours to diagnose. ubx's native test blocks address this directly: write assertions in a `.test.iac` file alongside your stack, and `ubx test` compiles the stack with the specified inputs and verifies the resulting resource plan matches your expectations. No deployment required, no cloud credentials needed, no Pulumi plan to interpret — just fast, local, compile-time verification that your `.iac` source produces the infrastructure you intend.

## What you'll learn

* How to write `test` blocks in `.test.iac` files
* The `inputs`, `assert`, `resource`, `action`, `attributes` structure
* How `expect_error` tests verify the compiler catches invalid configurations

***

## Why this matters

<Info>
  `ubx test` runs entirely locally — no cloud credentials, no network, no apply. Tests run in milliseconds because they're compile-time assertions against the planned resource graph, not integration tests against live infrastructure. Add them to CI to catch regressions before they reach staging.
</Info>

***

## The source code

<CodeGroup>
  ```hcl main.iac theme={"theme":"css-variables"}
  unit "aws_s3_bucket_v2" "assets" {
    bucket = "${input.env}-assets"
    tags = {
      Environment = input.env
      ManagedBy   = "ubx"
    }
  }

  unit "aws_rds_instance" "db" {
    engine                  = "postgres"
    instance_class          = "db.t3.micro"
    identifier              = "${input.env}-db"
    storage_encrypted       = true
    backup_retention_period = 7
  }
  ```

  ```hcl infra_test.test.iac theme={"theme":"css-variables"}
  # test blocks live in *.test.iac files — never compiled into the main stack.
  # ubx test runs them; ubx validate and ubx apply ignore them.

  # Plan assertion: compile with env=staging and verify bucket name.
  test "s3_bucket_naming" {
    inputs = { env = "staging" }
    assert {
      resource   = "aws_s3_bucket_v2.assets"
      action     = "create"
      attributes = { bucket = "staging-assets" }
    }
  }

  # Plan assertion with a different input value.
  test "rds_identifier_naming" {
    inputs = { env = "prod" }
    assert {
      resource   = "aws_rds_instance.db"
      action     = "create"
      attributes = { identifier = "prod-db" }
    }
  }

  # Error assertion: verify the compiler catches invalid references.
  # Guards against regressions in the type-checker.
  test "invalid_ref_is_caught" {
    source       = "unit \"aws_s3_bucket_v2\" \"x\" { bucket = unit.aws_s3_bucket_v2.nonexistent.id }"
    expect_error = "Undefined reference"
  }
  ```

  ```hcl inputs.iac theme={"theme":"css-variables"}
  input "env" {
    type    = "string"
    default = "staging"
  }
  ```

  ```hcl outputs.iac theme={"theme":"css-variables"}
  output "bucket_name" { value = unit.aws_s3_bucket_v2.assets.bucket }
  output "db_endpoint" { value = unit.aws_rds_instance.db.endpoint }
  ```

  ```hcl ubx.iac theme={"theme":"css-variables"}
  ubx {
    name    = "native-test-blocks"
    runtime = "typescript"
    stacks  = ["dev", "staging", "prod"]
  }

  backend "local" {
    path = ".ubx/state"
  }
  ```
</CodeGroup>

***

## How it works

<Steps>
  <Step title="ubx test discovers *.test.iac files">
    Running `ubx test` scans the project directory for files matching `*.test.iac`. These files are compiled separately from the main stack — they never appear in `ubx validate` output or the generated Pulumi program.
  </Step>

  <Step title="Each test compiles the stack with the specified inputs">
    For `test "s3_bucket_naming" { inputs = { env = "staging" } }`, ubx compiles `main.iac` with `env = "staging"` and builds the planned resource graph — the same graph `ubx plan` would produce, but computed entirely locally without contacting AWS.
  </Step>

  <Step title="Assertions are checked against the planned resource graph">
    `assert { resource = "aws_s3_bucket_v2.assets"; action = "create"; attributes = { bucket = "staging-assets" } }` checks that the compiled plan includes a create action on `aws_s3_bucket_v2.assets` with `bucket = "staging-assets"`. A mismatch fails the test with a clear diff.
  </Step>

  <Step title="expect_error tests verify compiler error behaviour">
    `expect_error = "Undefined reference"` compiles the given source snippet and asserts that the compiler produces an error matching that string. This guards against regressions in type-checker rules — if someone accidentally removes the undefined-reference check, this test fails.
  </Step>
</Steps>

***

## Common mistakes

<Warning>
  Test blocks use `resource = "type.label"` format — `"aws_s3_bucket_v2.assets"`, not `"unit.aws_s3_bucket_v2.assets"`. The `unit` keyword is omitted in test resource references, matching Pulumi's internal resource address format.
</Warning>

<Tip>
  Write tests for every non-trivial naming convention, every conditional expression that picks different values per environment, and every `for_each` expansion. These are the places most likely to silently produce wrong names when inputs change. A test with `inputs = { env = "prod" }` and `inputs = { env = "dev" }` covers both code paths in seconds.
</Tip>

***

## Run it

```bash theme={"theme":"css-variables"}
ubx test               # runs all *.test.iac files, no cloud credentials needed
ubx test --verbose     # shows each assertion result
ubx validate           # test files are ignored — main stack only
```

***

## What you learned

<Check>`test` blocks in `*.test.iac` files assert that compiled resource plans match expected attributes</Check>
<Check>`ubx test` runs entirely locally — no cloud credentials, no network, no apply</Check>
<Check>`expect_error` tests verify the compiler correctly catches invalid configurations</Check>

***

## Next steps

<CardGroup cols={2}>
  <Card title="AI plan summary and cost" href="/v1/tutorials/48-ai-plan-summary-and-cost">
    Get a plain-English summary of your plan
  </Card>

  <Card title="Security scan" href="/v1/tutorials/50-security-scan-clean-project">
    Enforce security rules at validate time
  </Card>
</CardGroup>

***

<Info>
  Full runnable example: [github.com/ubiquex/ubx-examples/47-native-test-blocks](https://github.com/ubiquex/ubx-examples/tree/main/47-native-test-blocks)
</Info>
