What you will be able to do
- Decide which kinds of test belong in development, CI and production runs, and explain why
- Explain how dbt build orders unit tests, materialization and data tests, and how failures cause downstream nodes to skip
- Use test_type selectors and indirect selection modes to run the right tests at each stage of the workflow
Key concept
Tests as a gate in the build — Tests are steps in the workflow, not a separate report you read afterwards. Inside dbt build, unit tests run before a model is materialized and data tests run after it, and a failure stops the downstream nodes. Where a test sits in that sequence decides how much bad logic or bad data can get through.
1.Which tests run in development, CI and production
Writing tests is half the job. The other half is deciding when they run. dbt has two families of test. Data tests are queries that return failing rows from a built model, source, seed or snapshot. Unit tests check a model's SQL logic against small mocked inputs. How to write each one is covered in [the test types lesson](lesson:5.1). Here the question is where each one fits in the path from a developer's branch to the production job.
Data tests run at two moments. While you write new code, they show whether a changed SQL statement has broken a model that used to pass. When transformations run in production, they check that your assumptions about the source data still hold. A payments source that suddenly sends a fourth payment method is not a code change, and only a production data test will catch it. Those source-level assumptions are the subject of [testing assumptions on models and sources](lesson:5.2).
Unit tests belong to development and CI only. The fixtures never change, so a test that passed on the pull request will pass again in production, and the only effect of rerunning it is a bill. In development, unit tests support a test-driven loop: write the expected rows, then change the SQL until it produces them. In CI, they stop a merge that would break logic which used to work. To keep them out of production builds, exclude the resource type with --exclude-resource-type. On dbt v1.11 and later, the DBT_ENGINE_EXCLUDE_RESOURCE_TYPES environment variable does the same job.
Checkpoint 1 of 8· Check yourself
A team runs unit tests on every pull request and wants its production job to stop spending compute on them. What should it change?
Excluding the resource type keeps the unit tests in the project, so CI still runs them, while production builds skip them. Moving the YAML into tests/ would not work, because that directory is reserved for data tests.
“to exclude unit tests from your production builds and save compute.”Source: docs.getdbt.com
Checkpoint 2 of 8· Exam question
A pull request modifies `stg_orders`. The team wants CI to build `stg_orders` and every downstream model in one invocation, and a failing test on `stg_orders` must stop those downstream models from running at all. Which command accomplishes this?
Correct answer: A — Run `dbt build --select stg_orders+`, since build interleaves tests into DAG order so a failing test blocks descendants from materializing in that run.
- A. `dbt build` runs seeds, models, snapshots, and tests together in DAG order, so a failing test on `stg_orders` prevents dependent models from materializing within the same invocation.
- B. Running models first with `dbt run` already materializes every downstream model before the test suite executes, so a later test failure cannot stop anything from having built.
- C. Testing before running fails outright, because a data test queries the model's relation, which does not yet exist until a build or run step creates it.
- D. Repeating the build on `stg_orders` alone omits the `+` operator needed to include descendants, so downstream models are never selected and never run at all.
2.dbt test versus dbt build
dbt test checks resources that already exist. It runs data tests on models, sources, snapshots and seeds, plus unit tests on SQL models. It does not build anything first. If you run it straight after editing a model, the data tests query whatever version of the table is still in the warehouse. The full command set is covered in [dbt commands](lesson:1.4).
dbt build combines the steps. It runs models, tests tests, snapshots snapshots and seeds seeds, all in DAG order. Each resource is tested soon after it is built, so a node never starts on top of an upstream node that has not been checked yet. The task also writes a single run results artifact covering every model, test, seed and snapshot it selected. A CI system can read that one file to decide whether the run passed.
18:49:43 | 1 of 7 START seed file dbt_jcohen.my_seed............................ [RUN]
18:49:43 | 1 of 7 OK loaded seed file dbt_jcohen.my_seed........................ [INSERT 2 in 0.09s]
18:49:43 | 2 of 7 START view model dbt_jcohen.my_model.......................... [RUN]
18:49:43 | 2 of 7 OK created view model dbt_jcohen.my_model..................... [CREATE VIEW in 0.12s]
18:49:43 | 3 of 7 START test not_null_my_seed_id................................ [RUN]
18:49:43 | 3 of 7 PASS not_null_my_seed_id...................................... [PASS in 0.05s]
18:49:43 | 4 of 7 START test unique_my_seed_id.................................. [RUN]
18:49:43 | 4 of 7 PASS unique_my_seed_id........................................ [PASS in 0.03s]
18:49:43 | 5 of 7 START snapshot snapshots.my_snapshot.......................... [RUN]
18:49:43 | 5 of 7 OK snapshotted snapshots.my_snapshot.......................... [INSERT 0 5 in 0.27s]
18:49:43 | 6 of 7 START test not_null_my_model_id............................... [RUN]
18:49:43 | 6 of 7 PASS not_null_my_model_id..................................... [PASS in 0.03s]
18:49:43 | 7 of 7 START test unique_my_model_id................................. [RUN]
18:49:43 | 7 of 7 PASS unique_my_model_id....................................... [PASS in 0.02s]When a model has unit tests, dbt build adds a step in front of materialization. The unit tests run against the model's SQL first. The model is materialized only if they pass, and its data tests run after that. If the logic is broken, the build stops before it pays to write a full table that would be wrong anyway.
Checkpoint 3 of 8· Put it in order
Put the steps dbt build performs for a SQL model with unit tests in order
- 1.Data tests are run on the model
- 2.The model is materialized
- 3.Unit tests are run on the SQL model
Unit tests need only the model's SQL and mocked inputs, so they can run before anything is built. Data tests query the built relation, so they have to come after materialization.
“This saves on warehouse spend as the model will only be materialized if the unit tests pass successfully.”Source: docs.getdbt.com
3.How a failing test stops the rest of the DAG
dbt build uses test failures as a gate. If a test on model_a fails, every resource downstream of model_a is marked SKIP. Duplicate keys that slip into a staging model stop there, instead of spreading into every mart that joins on them. Running dbt run and then dbt test gives you the same tests but not the gate. By the time the tests report a problem, the downstream tables have already been rebuilt from the bad data.
Some tests check things you want to know about without stopping the pipeline, such as a soft data-quality signal. For those, set the test's severity, or its thresholds, to warn instead of error. A warning appears in the results and downstream nodes still run.
Tests with more than one parent follow a narrower rule. A relationships test between model_a and model_b depends on both models, and model_b already depends on model_a. In that case the test blocks and skips only the children of the most-downstream parent, which is model_b. If a test's parents are independent of each other, dbt skips a downstream node only when that node depends on all of those parents.
A failure in development raises a practical question: which rows failed? The --store-failures flag, or the store_failures config, saves each failing test's result rows to a table. By default the table goes in a schema named or suffixed dbt_test__audit. Each new run of a test replaces the failures stored for that test, so the table always shows the latest run.
Checkpoint 4 of 8· Check yourself
A not_null test on stg_payments flags a few rows each week. The team wants to see those rows but does not want them to stop the marts built on stg_payments during dbt build. What is the right change?
Downstream nodes are skipped only because the test fails as an error. Changing its severity to warn keeps the test visible in the results and lets the marts build.
“Don't want a test to cause skipping? Adjust its severity or thresholds to warn instead of error”Source: docs.getdbt.com
Checkpoint 5 of 8· Exam question
A `not_null` test on `stg_events.session_id` fails for a small, accepted percentage of rows that the data team already knows about, but they still want every CI run to surface how many rows are affected instead of silently ignoring the column. Which two changes together achieve this?(Select 2)
Correct answers: A, B — Set `config: severity: warn` on the test so the run reports the failure without blocking the pipeline.; Add `config: store_failures: true` so the failing rows land in a queryable audit table each run.
- A. Setting the test severity to warn keeps the row count visible in the run output while letting the pipeline continue past the known, accepted nulls.
- B. Storing failures writes the offending rows to a `dbt_test__audit` table each run, giving the team a queryable record of exactly which rows failed without stopping the build.
- C. Removing the test entirely stops both the failure and the row-count visibility the team explicitly wants to keep, so it does not satisfy the requirement.
- D. Enumerating every observed value in an `accepted_values` list does not test for nulls at all, and it requires maintaining a growing list by hand instead of reporting the null count.
- E. Raising the failure threshold to an unrealistic number stops the test from failing but also hides a genuine spike in nulls, defeating the goal of keeping the issue visible.
4.Selecting the right tests for each stage
The test_type selector looks at a property of the test itself, not at the model it is attached to. That makes it the tool for running each kind of test where it belongs. test_type:unit and test_type:data split the two families. test_type:generic and test_type:singular split data tests further, into YAML-defined assertions and the custom SQL files in tests/. These selectors work with --select and --exclude on both dbt test and dbt build.
# run data and unit tests
dbt test
# run only data tests
dbt test --select test_type:data
# run only unit tests
dbt test --select test_type:unit| Stage | Typical selection | Why |
|---|---|---|
| Development, while changing tricky SQL | dbt test --select "test_type:unit" | Fast feedback on logic before the full model is materialized |
| CI, before merge | dbt build (unit tests, then the model, then its data tests) | Changed logic and existing assumptions are both checked, and the run fails on a broken change |
| Production | dbt build with --exclude-resource-type for unit tests, or dbt test --select "test_type:data" | Source data changes between runs; unit test fixtures never do |
You can combine a selector with a model to narrow the run. A comma means intersection, so "one_specific_model,test_type:data" runs only the data tests on that model. This is useful when you have just rebuilt one model and want to check its outputs without waiting for the whole project's test suite.
Checkpoint 6 of 8· Fill the gap
You have rewritten the regex in one model and want to run only that model's unit tests. Which value completes the command?
dbt test --select "one_specific_model,test_type: ? "test_type:unit intersected with the model runs only the unit tests attached to that model. data, singular and generic all select data tests, which check built tables rather than SQL logic.
Source: docs.getdbt.com5.Testing a subset of the DAG with indirect selection
When you select a model, dbt also picks up the tests that depend on it. This is indirect selection, so dbt build -s model_a both runs model_a and tests it. The difficulty is tests with several parents. Under the default mode, eager, a test runs if any of its parents is selected, even if its other parents were never built in this run. A relationships test from orders to an unbuilt customers model can then fail for reasons that have nothing to do with your change.
Three other modes narrow the selection. buildable runs a test only when everything it references is a selected node or an ancestor of one. That suits a test comparing an aggregate with its own input. cautious runs only tests whose every parent is selected. empty runs no tests at all. You set the mode with --indirect-selection on either dbt test or dbt build. Exclusion always applies to every parent: if any parent is explicitly excluded, the test is excluded too.
dbt test --select "orders" --indirect-selection=cautious
dbt build --select "orders" --indirect-selection=cautiousCheckpoint 7 of 8· Match them up
Match each indirect selection mode to the tests it runs for a selected model
Tap a term, then the definition that fits it.
The modes run from most to least inclusive. Choose buildable or cautious when you build only part of the DAG and want to avoid failures caused by parents you did not build.
“useful when you're only building a subset of your DAG, and you want to avoid test failures in eager mode caused by unbuilt resources.”Source: docs.getdbt.com
Checkpoint 8 of 8· Exam question
The team added new unit tests for `dim_customers` and wants to run just those unit tests before merging, without re-running the model's existing data tests. Which single command does this?
Correct answer: A — Run `dbt test --select dim_customers,test_type:unit` to execute only the unit tests defined for that model.
- A. Combining the model name with `test_type:unit` scopes the run to exactly the unit tests defined on `dim_customers`, skipping its data tests and skipping materialization.
- B. `dbt build` on the model runs the model itself and every test type configured on it in DAG order, which is broader than the unit-tests-only scope requested.
- C. `test_type:data` selects the built-in and singular tests that assert against already-materialized data, which is the opposite of the pre-materialization unit tests being asked for.
- D. `dbt run` only materializes the model and does not execute any tests, unit or otherwise, so it does not satisfy a request to run tests at all.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Unit tests should run in the production job too, so production has the fullest coverage.Why is that wrong?
Unit tests use static fixtures, so a production run repeats the CI result and only costs compute. Run them in development and CI, and exclude them from production builds.
Covered in Which tests run in development, CI and production
2.In dbt build, unit tests run after the model is materialized, alongside its data tests.Why is that wrong?
Unit tests run first. The model is materialized only if they pass, and its data tests run after that.
Covered in dbt test versus dbt build
3.dbt build covers everything, including source freshness checks.Why is that wrong?
Freshness is not part of build. It needs its own dbt source freshness step or the job's freshness checkbox.
Covered in dbt test versus dbt build
4.A failing test in dbt build is only reported, and downstream models still build.Why is that wrong?
A test failure at error severity causes the downstream resources to skip. Only changing the severity to warn lets them run.
Covered in How a failing test stops the rest of the DAG
Practise it for real
Run your project's tests the way each stage of the workflow would, and watch the build act as a gate
1.Run dbt test --select test_type:unit in your development target.
Why: This is the fast development loop. Only the SQL logic is checked against fixtures.
You should see: Only unit_test nodes appear in the output. No data tests run and no models are built.
2.Run dbt build -s one_specific_model, replacing the name with a model in your project that has both unit tests and data tests.
Why: Indirect selection brings in the model's tests, and build orders them around materialization.
You should see: The unit tests run before the model is created and the data tests run after it.
3.Break a column so that a unique test on an upstream model fails, then run dbt build again for that model and its children.
Why: This shows the skip-on-failure gate working.
You should see: The test reports FAIL and the downstream models report SKIP.
4.Set that test's severity to warn and rerun the build.
Why: Severity decides whether a failure blocks the DAG.
You should see: The test shows as WARN and the downstream models build.
5.Run dbt test --select "one_specific_model,test_type:data" with your model's name.
Why: This is the production-style check: data tests on one model, with no unit tests.
You should see: Only the generic and singular data tests attached to that model run.
Stuck? Get a nudge
If a relationships test fails during a partial build because its other parent was never built, rerun with --indirect-selection=cautious.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://docs.getdbt.com/faqs/Tests/when-to-testOfficial docs
“whenever you run your transformations in production (to ensure that your assumptions about your source data are still valid)”
↩︎ Which tests run in development, CI and production - 2.https://docs.getdbt.com/docs/build/data-testsOfficial docs
“You should run your data tests whenever you are writing new code (to ensure you haven't broken any existing models by changing SQL)”
↩︎ Which tests run in development, CI and production“Test result tables are created in a schema suffixed or named dbt_test__audit, by default.”
↩︎ How a failing test stops the rest of the DAG“A test's results will always replace previous failures for the same test.”
↩︎ How a failing test stops the rest of the DAG - 3.https://docs.getdbt.com/docs/build/unit-testsOfficial docs
“Use the resource type flag --exclude-resource-type”
↩︎ Which tests run in development, CI and production“Use them in development for a test-driven approach and CI to ensure changes don't break them.”
↩︎ Which tests run in development, CI and production“dbt Labs strongly recommends only running unit tests in development or CI environments.”
↩︎ Exam trap 1“Since the inputs of the unit tests are static, there's no need to use additional compute cycles running them in production.”
↩︎ Prediction“to exclude unit tests from your production builds and save compute.”
↩︎ Checkpoint - 4.https://docs.getdbt.com/reference/commands/testOfficial docs
“It expects that you have already created those resources through the appropriate commands.”
↩︎ dbt test versus dbt build - 5.https://docs.getdbt.com/reference/commands/buildOfficial docs
“In DAG order, for selected resources or an entire project.”
↩︎ dbt test versus dbt build“The run results will include information about all models, tests, seeds, and snapshots that were selected to build, combined into one file.”
↩︎ dbt test versus dbt build“that test will block-and-skip children of the most-downstream parent only (model_b).”
↩︎ How a failing test stops the rest of the DAG“Unit tests and data tests can be selected using --select test_type:unit or --select test_type:data for dbt build (same for the --exclude flag).”
↩︎ Selecting the right tests for each stage“Remember that tests support indirect selection, so dbt build -s model_a will both run and test model_a.”
↩︎ Testing a subset of the DAG with indirect selection“This saves on warehouse spend as the model will only be materialized if the unit tests pass successfully.”
↩︎ Key concept“This saves on warehouse spend as the model will only be materialized if the unit tests pass successfully.”
↩︎ Exam trap 2“Tests on upstream resources will block downstream resources from running, and a test failure will cause those downstream resources to skip entirely.”
↩︎ Exam trap 4“This saves on warehouse spend as the model will only be materialized if the unit tests pass successfully.”
↩︎ Checkpoint“Tests on upstream resources will block downstream resources from running, and a test failure will cause those downstream resources to skip entirely.”
↩︎ Prediction“Don't want a test to cause skipping? Adjust its severity or thresholds to warn instead of error”
↩︎ Checkpoint - 6.
“running only unit tests during development for fast feedback, or running only data tests in production where unit test compute isn't needed.”
↩︎ Selecting the right tests for each stage“Restricts tests to only those that exclusively reference selected nodes.”
↩︎ Testing a subset of the DAG with indirect selection“Test exclusion is always greedy: if ANY parent is explicitly excluded, the test will be excluded as well.”
↩︎ Testing a subset of the DAG with indirect selection“useful when you're only building a subset of your DAG, and you want to avoid test failures in eager mode caused by unbuilt resources.”
↩︎ Checkpoint
Also cited
“dbt build does not include source freshness checks when building and testing resources in your DAG.”
↩︎ Exam trap 3