How tests work
A test file defines mock inputs and expected outputs using CTEs. SQLBuild substitutes the mock data into the real model SQL, executes it, and compares the result against the expected CTE usingEXCEPT queries. Zero mismatched rows means the test passes.
- Mocks the
raw__orderssource with the__source__raw__ordersCTE - Runs the real
stg_ordersmodel SQL with the mock substituted in - Compares the output against
__expected__stg_orders - Passes if row counts match and there are zero mismatched rows
unexpected_samples and missing_samples in JSON output. Sampling failure never
replaces the known test failure.
When exactly one row exists in each difference direction and the bounded samples have the same
columns, SQLBuild also aligns the rows and reports only changed columns with their redacted actual
and expected values. It does not guess an alignment when several rows differ or sampling is
incomplete.
Before opening a warehouse connection, SQLBuild statically checks fixture shapes when SQL analysis
is enabled. Missing columns are reported together with the test path, fixture resource, and models
that read them. Statically provable collection-versus-scalar type conflicts identify the affected
column and suggest explicit CAST, ARRAY_CONSTRUCT, or PARSE_JSON expressions. SQLBuild never
invents missing values.
The trailing SELECT 1 is required as a ceremonial closing statement.
CTE conventions
Any CTE without one of these prefixes is treated as a helper CTE, available to all mock and model SQL in the test.
Multi-model tests
Tests can span multiple models in a single file. Mock your sources, define an expected output for the model you care about, and SQLBuild automatically resolves every intermediate model using its real SQL.Mocking refs and seeds
You can mock models directly with__ref__<name> and seeds with __seed__<name>, not just sources. This skips the model’s real SQL (or the seed’s real CSV data) and provides controlled data instead:
__source__, __ref__, and __seed__ mocks in the same test. As long as every leaf dependency is satisfied (either by a source mock, a ref mock, a seed mock, or by being in the expected chain), the test resolves.
Multiple expected models
A single test can assert on multiple models. SQLBuild resolves and compares each one independently:stg_orders feeds into fact_orders which feeds into dim_customers), SQLBuild resolves them in dependency order, using the output of earlier steps as input to later ones.
When a test defines __expected__model_name output, it may also use the public enums and constants
available to that model. A test that checks several models may use the public enums and constants
available to each of them. Public names are unique, so those values cannot conflict.
Only explicit expected CTEs create grants. A matching test filename, __ref__ mock, or nearby model path does not. Model-private declarations and macros are never granted through expected models.
Macro-powered mocks
Because unit tests are written in SQL, they support macro calls. This lets you write reusable mock generators instead of copy-pasting mock data across test files:@mock_orders() call expands at compile time to whatever SQL the Python macro function returns.
Test SQL uses macros, enums, and constants available from the test file’s directory under
tests/unit/. Public enums and constants available to a model are also available when the test
defines __expected__model_name output for that model. Model-private values are not available to
tests. See How Visibility Works.
Macro mocking
When a model uses macros that you want to control in tests (e.g. target-specific logic, dynamic SQL generation), you can override their output with__macro__<name> CTEs:
__macro__ mock is defined, every call to @country_filter(...) in any model SQL resolved by the test is replaced with the mock value (country_code = 'US'). The macro’s actual Python function is not called, and the arguments are ignored.
The mock value must be a single SELECT with one string literal. Use doubled single quotes for quotes within the value (standard SQL escaping).
This is useful for:
- Testing models that use target-specific macros without depending on target config
- Controlling dynamic SQL generation to produce predictable test inputs
- Isolating model logic from macro implementation details
Assertions
Unit tests can include__assert__<name> CTEs for property-based checks. An assertion passes if the query returns zero rows - any returned rows are failing examples.
__expected__ CTEs in the same test, or used on their own. They are useful when the natural check is “no rows should violate this rule” rather than “the output should exactly equal these rows” - for example, duplicate checks, negative-value constraints, or conditional business rules.
During sqb test and sqb build, assertion results appear as nested check rows alongside expected comparisons.
Test modes
By default,TEST() runs in model mode - mocking sources/refs and comparing model outputs. Three additional modes let you test reusable logic directly without needing a model chain.
Macro tests
Test macro output by calling the macro in__macro_actual__ and comparing against __macro_expected__:
sqb build, macro tests run before any model that uses the tested macro.
UDF tests
Test scalar UDFs by calling them in__udf_actual__ and comparing against __udf_expected__:
sqb build, UDF tests run after the function is created but before any model that uses it.
Table function tests
Test table functions by calling them in__table_fn_actual__ and comparing against __table_fn_expected__:
Mode rules
Each mode has strict CTE validation:
CTE prefixes from other modes are not allowed. For example,
__source__ in a macro test or __macro_actual__ in a model test will produce a clear error pointing you to the right mode. Expected CTEs must not call macros, UDFs, or table functions - they should be independent, inspectable expected data.
Multiple tests per file
A single test file can contain multipleTEST() blocks. Each block must have a unique name:
name field. Files with multiple tests require names on every block.
Repeating test logic
SQLBuild supports three repetition patterns:Native independent cases
Declare a typed schema and ordered named cases in one header:order status: maps source states [completed], [cancelled],
and [pending]. Authored order controls display order. Selecting stg_orders selects every case by
default. Add --case to run one named case without changing the test file:
string, integer, boolean, float, and exact decimal. Decimal
values are quoted, such as tax_rate "0.2000", so they never pass through binary float. Boolean
and integer remain distinct. Raw SQL and collection parameters are rejected.
Nullable parameters use an expanded declaration and are valid where SQL can infer the null type:
@param("name") in the template. SQLBuild rejects missing, extra, incompatible, undeclared, and
unused values before execution.
Expansion is deterministic:
@order_fixture(@param("source_status")). Parameters work in model, macro, UDF, and table-function
modes and preserve every expected model in multi-model tests.
Text and JSON output include parent/case identity and safe typed values. Compile JSON, manifests,
DAG checks, and compiled/runtime SQL artifacts retain source, block, case, and content-fingerprint
provenance. Changing one case changes that case’s fingerprint without reassigning another case’s
stable identity.
Scenarios are intentionally not parameterized. Keep one scenario per coherent business world so
capture and replay identity remains explicit.
Aggregate SQL case tables
When independent status is unnecessary, a normal SQL case table remains concise:VALUES rows may contain commas, brackets, and parentheses. SQLBuild preserves
these literals while extracting test CTEs; use normal SQL quote escaping for embedded quotes.
Test placement
Place unit test files undertests/unit/ in your project directory. SQLBuild discovers all .sql files in this directory recursively.
Running tests
Tests run automatically duringsqb build in DAG order, before their target model is materialized.
Run tests standalone:

