dbt test Command: Running Validation and Reading Failures Correctly
dbt test is the command that turns the schema tests and singular tests covered in dbt Tests from declarations into actual validation. Itโs a separate command from dbt run deliberately โ building and validating are different concerns, and treating them separately is what lets you run tests without rebuilding everything, or rebuild without waiting on the full test suite during rapid local iteration.
Running the Full Test Suite
dbt testEvery generic test attached to every model, source, seed, and snapshot in the project runs, along with every singular test file in tests/. Output tells you pass/fail per test, plus how many rows failed for row-count-based failures:
1 of 6 START test not_null_stg_orders_order_id ......... [RUN]1 of 6 PASS not_null_stg_orders_order_id ................ [PASS in 0.31s]2 of 6 START test unique_stg_orders_order_id ............ [RUN]2 of 6 FAIL 3 unique_stg_orders_order_id ................ [FAIL 3 in 0.42s]...Done. PASS=5 WARN=0 ERROR=0 FAIL=1 SKIP=0 TOTAL=6Scoping Test Runs
Running every test in a large project on every local change is slow. Selection syntax scopes exactly what you want validated.
# Tests for one modeldbt test --select stg_orders
# Tests for a model and everything downstreamdbt test --select stg_orders+
# Only source testsdbt test --select source:*
# Only tests of a specific typedbt test --select test_type:singulardbt test --select test_type:generictest_type:singular and test_type:generic are particularly useful when debugging โ if you suspect the problem is in a custom business-logic check rather than a basic uniqueness violation, scoping to singular tests only cuts out noise from the (usually much larger) set of generic tests.
Storing Failures for Inspection
By default, a failed test tells you how many rows failed, but not which ones โ youโd need to manually run the compiled test query to see them. --store-failures changes that, materializing the actual failing rows into a table you can query directly.
dbt test --select unique_stg_orders_order_id --store-failuresselect * from analytics.dbt_test_audit.unique_stg_orders_order_idThis is enabled globally via config rather than per-invocation for teams that want it as a standing behavior:
tests: +store_failures: true +schema: dbt_test_auditFor any project with more than a handful of models, store_failures turns โa test failedโ from a debugging session into a two-second query against the exact rows that violated it.
Running Tests Against a Full Refresh
Tests inherently run against whatever data currently exists in the warehouse โ if a model hasnโt been rebuilt since a fix, its tests will still reflect the old, broken data. A common local workflow when debugging a failing test:
dbt run --select stg_orders --full-refreshdbt test --select stg_ordersRebuilding before re-testing avoids the confusing scenario where youโve fixed the SQL but the test still fails because itโs checking stale, previously-built data.
Indirect Selection: Testing Only What You Touched
By default, if you select a model with --select, dbt also runs any tests that reference multiple models where at least one is in your selection โ even if you didnโt explicitly select the other model. This is called indirect selection, and it can be tuned:
# Only run tests fully contained within the selected modelsdbt test --select stg_orders --indirect-selection=cautious
# Run tests as long as any referenced model is in the selection (default-ish behavior)dbt test --select stg_orders --indirect-selection=eagerThis matters most for relationships tests, which reference two models by nature โ the indirect selection mode determines whether changing one side of that relationship is enough to trigger the test, or whether both sides need to be explicitly selected.
Exit Codes for CI Integration
dbt test returns a non-zero exit code on any test failure, which is what makes it usable as a CI gate without any additional parsing logic โ a CI step running dbt test will correctly fail the pipeline the moment any test fails.
# Example CI step (pseudocode)- run: dbt test # Pipeline automatically fails here if dbt test returns non-zero- run: deploy_to_productionRunning Tests With Increased Verbosity for Debugging
When a testโs default output isnโt enough to understand why it failed, the --debug flag surfaces the actual compiled SQL dbt is executing for that test, alongside connection details โ useful when a test failure seems inconsistent with what youโd expect from reading the YAML alone.
dbt --debug test --select unique_stg_orders_order_idThis prints the exact query dbt sent to the warehouse, which is often the fastest way to spot a subtle issue โ a test unexpectedly scoped to the wrong schema because of an environment misconfiguration, for instance, is usually obvious the moment you see the actual compiled query rather than just the pass/fail result.
Common Mistakes
Forgetting to rebuild before re-testing after a fix. Tests validate whateverโs currently in the warehouse, not your latest, unbuild SQL changes.
Not using --store-failures on a project with frequent test failures. Without it, every failure investigation requires manually re-running the compiled test SQL โ a repeatable inefficiency that --store-failures eliminates entirely.
Misunderstanding indirect selection and being surprised by which tests ran (or didnโt). If a relationships test unexpectedly ran (or didnโt) after a scoped selection, check the indirect selection mode before assuming a bug in dbt itself.
Summary
| Flag | Purpose |
|---|---|
--select | Scopes which tests run |
--store-failures | Materializes failing rows into a queryable table |
--indirect-selection | Controls whether multi-model tests run on partial selections |
| Non-zero exit code on failure | Makes dbt test usable directly as a CI gate |
dbt test is where a projectโs data quality claims get checked against reality on every run. Used with scoped selection and --store-failures during active development, it turns โsomethingโs wrong somewhereโ into a fast, specific, queryable answer.