> For the complete documentation index, see [llms.txt](https://v2.dataos.info/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://v2.dataos.info/references/dataos-resources/vulcan/core-concepts/unit-test.md).

# Test

A test validates a model's transformation logic against fixtures: predefined inputs and expected outputs you write by hand. Where [assertions](/references/dataos-resources/vulcan/core-concepts/assertions.md) check runtime data quality after a model runs, tests verify the logic of the transformation itself against known inputs, independent of any real data.

Tests fit alongside the other data quality artifacts in Vulcan. The mental model across all of them: **DQ checks watch, audits define, assertions attach, and tests validate logic.** A [DQ check](/references/dataos-resources/vulcan/core-concepts/data-quality.md) monitors quality over time, an audit is a reusable rule, an assertion attaches that rule to a model, and a test proves the model's SQL produces the right output for a given input.

Tests run on demand, such as in a CI/CD pipeline, or automatically when you apply a [plan](/references/dataos-resources/vulcan/core-concepts/plan.md).

## Creating tests

Tests live in YAML files in the `tests/` folder. The filename must start with `test` and end in `.yaml` or `.yml`. You can put multiple tests in one file. At minimum, a test needs three keys:

* `model`: the model you are testing.
* `inputs`: mock data for upstream dependencies (what goes in).
* `outputs`: expected results from the model's query (what should come out).

Here is a test for a `daily_sales` model that aggregates orders by date:

```yaml
test_daily_sales_aggregation:
  model: sales.daily_sales
  description: Aggregates orders by date with counts and revenue.
  inputs:
    raw.raw_orders:
      rows:
        - order_id: O001
          order_date: '2024-03-15'
          total_amount: 50.00
        - order_id: O002
          order_date: '2024-03-15'
          total_amount: 75.00
        - order_id: O003
          order_date: '2024-03-16'
          total_amount: 100.00
  outputs:
    query:
      rows:
        - order_date: '2024-03-15'
          total_orders: 2
          total_revenue: 125.00
        - order_date: '2024-03-16'
          total_orders: 1
          total_revenue: 100.00
```

The test feeds the model three orders and asserts they are grouped by date, counted, and summed correctly. If any value does not match, the test fails and shows what went wrong.

{% hint style="info" %}
Shortcut: if `rows` is the only key under an input model, you can omit the `rows:` level entirely. For example, `vulcan_demo.orders: [{order_id: 1}]` is equivalent to `vulcan_demo.orders: {rows: [{order_id: 1}]}`.
{% endhint %}

## Key capabilities

### Multiple dependencies

Provide mock data for each upstream model a target model joins. List them side by side under `inputs`:

```yaml
  inputs:
    vulcan_demo.customers:
      - customer_id: 1
        name: Alice
    vulcan_demo.orders:
      - order_id: 1001
        customer_id: 1
```

### Incremental models

Incremental models filter by time range. Set `start` and `end` under `vars` to control the `@start_ds` and `@end_ds` macros:

```yaml
  vars:
    start: '2025-01-01'
    end: '2025-01-02'
```

### Testing CTEs

Test individual CTEs to debug complex queries step by step, using `outputs.ctes.<cte_name>.rows`:

```yaml
  outputs:
    ctes:
      filtered_orders_cte:
        rows:
          - id: 1
            item_id: 1
    query:
      rows:
        - item_id: 1
          num_orders: 2
```

### Data formats

Input data defaults to `yaml` dictionaries. You can also use `csv` (tune parsing with `csv_settings`, such as `sep`), a SQL `query`, or an external file via `path`:

```yaml
  inputs:
    vulcan_demo.orders:
      format: csv
      csv_settings:
        sep: '#'
      rows: |
        order_id#customer_id
        1001#1
```

{% hint style="info" %}
`query` and `rows` are mutually exclusive on the same input. Pick one.
{% endhint %}

### Partial matching

For wide tables, set `partial: true` to test only the columns you list. Omitted columns are treated as `NULL`:

```yaml
  outputs:
    query:
      partial: true
      rows:
        - customer_id: 1
          total_spent: 325
```

### Freezing time

If your model uses `CURRENT_TIMESTAMP` or `CURRENT_DATE`, freeze time with `vars: execution_time:` so tests stay deterministic:

```yaml
  vars:
    execution_time: '2023-01-01 12:05:03'
```

## Running tests

```bash
# Run all tests
vulcan test

# Run a specific file
vulcan test tests/test_daily_sales.yaml

# Run a single test with the :: syntax
vulcan test tests/test_daily_sales.yaml::test_daily_sales_aggregation

# Run tests matching a glob pattern
vulcan test tests/test_*
```

When tests pass, each dot is a passing test:

```
$ vulcan test
..
----------------------------------------------------------------------
Ran 2 tests in 0.024s

OK
```

When a test fails, the output shows the `exp` (expected) and `act` (actual) mismatch:

```
$ vulcan test
F
======================================================================
FAIL: test_daily_sales_aggregation (tests/test_daily_sales.yaml)
----------------------------------------------------------------------
AssertionError: Data mismatch (exp: expected, act: actual)

  total_orders
         exp  act
0        3.0  2.0
```

## Automatic generation

Bootstrap a test from real warehouse data with `create_test`. It pulls actual rows from an upstream model, which you can then refine:

```bash
vulcan create_test vulcan_demo.daily_sales \
  --query raw.raw_orders "SELECT * FROM raw.raw_orders LIMIT 10"
```

## Troubleshooting

* **Inspect fixtures.** Run with `--preserve-fixtures` to keep the test views (in a `vulcan_test_<id>` schema) and query them directly.
* **Type mismatches.** If Vulcan infers the wrong types, declare them explicitly under `inputs.<model>.columns:`.
* **Model not found.** Use the schema-qualified, case-sensitive name (such as `sales.daily_sales`, not `daily_sales`).
* **Data looks right but fails.** Columns in `outputs` must appear in the same order as the model's `SELECT`.
* **Partial matching not working.** `partial: true` can be set at two levels: `outputs.partial` (global, applies to all output sections) and `outputs.query.partial` (applies to the query section only). If partial matching is not working, confirm you are setting it at the right level.

## Test keys reference

| Key                           | Description                                                                                                                                                                      |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`                       | Fully qualified name of the model under test. Required.                                                                                                                          |
| `description`                 | Optional explanation of what the test validates.                                                                                                                                 |
| `schema`                      | Schema holding the test fixture views. Defaults to a temporary schema.                                                                                                           |
| `gateway`                     | Gateway whose `test_connection` runs the test. Defaults to the default gateway.                                                                                                  |
| `inputs`                      | Mock data for upstream models. Omit if the model has no dependencies.                                                                                                            |
| `outputs`                     | Expected results: `query`, `ctes.<name>`, and an optional `partial`.                                                                                                             |
| `vars`                        | Macro variables: `start` and `end` for incremental range, `execution_time` to freeze time.                                                                                       |
| `partial`                     | Test only the listed columns; omitted columns treated as `NULL`.                                                                                                                 |
| `inputs.<model>.rows`         | Rows of mock data as YAML dictionaries.                                                                                                                                          |
| `inputs.<model>.format`       | Input data format: `yaml` (default) or `csv`.                                                                                                                                    |
| `inputs.<model>.query`        | Generate input via SQL. Mutually exclusive with `rows`.                                                                                                                          |
| `inputs.<model>.csv_settings` | CSV parsing options (such as `sep`) when `format: csv`. All options map to `pandas.read_csv` keyword arguments. See the pandas documentation for the full list of accepted keys. |
| `inputs.<model>.path`         | Load input from an external file.                                                                                                                                                |
| `inputs.<model>.columns`      | Explicit column types to override inference.                                                                                                                                     |
| `outputs.query.query`         | Generate expected output using a SQL query instead of inline rows. Mutually exclusive with `outputs.query.rows`.                                                                 |
| `outputs.ctes.<name>.query`   | Generate expected CTE output via SQL. Mutually exclusive with `outputs.ctes.<name>.rows`.                                                                                        |
| `outputs.ctes.<name>.partial` | Test only the listed columns for this CTE; omitted columns treated as NULL.                                                                                                      |

## Related docs

* [Assertions](/references/dataos-resources/vulcan/core-concepts/assertions.md): audits and assertions that block bad rows at write time.
* [Data quality](/references/dataos-resources/vulcan/core-concepts/data-quality.md): DQ checks (`kind: dq`) that monitor quality over time.
* [Plan](/references/dataos-resources/vulcan/core-concepts/plan.md): how tests run automatically during plan apply.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://v2.dataos.info/references/dataos-resources/vulcan/core-concepts/unit-test.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
