FlashGenius Logo FlashGenius
dbt Certification · Domain 4 of 6

dbt Analytics Engineering Certification — Implementing dbt Tests

Generic tests, singular tests, custom generics, test configs, store_failures, and testing sources — the quality layer of every dbt project.

~15%Exam Weight
4 Test TypesTo Master
High PracticalSkill Focus
Config-HeavyQuestion Style

The 4 Test Types

dbt has four test categories. Know when to use each.

🔵

Generic Tests

Built-in: unique, not_null, accepted_values, relationships. Applied in .yml on any model or source column.

📄

Singular Tests

Custom SQL files in tests/ that return rows on failure. Highly flexible, model-specific logic.

🔧

Custom Generic

Reusable parameterized tests defined as macros. Like generic tests but you write the logic.

📦

Package Tests

Tests from installed packages (e.g. dbt_expectations, dbt_utils). Applied like generic tests.

🎯 Key Exam Insight

The exam frequently tests test configuration parameters: where, severity, warn_if, error_if, and store_failures. Know what each does and when to use it. A common scenario: "test only recent data" → use where. "Test large incremental without scanning all rows" → use where or a contract NOT NULL constraint.

Key Concepts Deep Dive

🔵
Generic Tests (Built-in)
unique · not_null · accepted_values · relationships
Very High Frequency
  • unique — asserts every value in a column is unique; fails if duplicates exist
  • not_null — asserts no NULLs in a column
  • accepted_values — asserts all values are in a specified list; takes a values: parameter
  • relationships — asserts referential integrity; every value in column A exists in column B of another model
  • Applied in .yml under tests: at the column or model level
  • Tests are SQL SELECT queries that return rows on failure — 0 rows = pass, any rows = fail
💡 Generic tests compile to SELECT queries. A not_null test generates: SELECT * FROM model WHERE column IS NULL. Zero rows returned = pass.
📄
Singular Tests
Custom SQL in the tests/ directory
Medium Frequency
  • SQL files in the tests/ directory (e.g., tests/assert_total_revenue_positive.sql)
  • Must return 0 rows to pass — any returned rows indicate a failure
  • Can use ref() and source() to reference models
  • Best for complex business logic tests that don't fit generic patterns
  • Not reusable across models (unlike custom generic tests)
  • Run with dbt test — singular tests are picked up automatically from the tests/ folder
💡 Use singular tests for one-off business rules: "total revenue must always be positive", "order date must never be in the future".
🔧
Custom Generic Tests
Reusable parameterized test macros
Medium Frequency
  • Defined as Jinja macros in the tests/generic/ or macros/ directory
  • Take model and column_name as required arguments; can accept additional parameters
  • Applied in .yml exactly like built-in generic tests
  • Ideal when the same custom validation logic needs to be applied to many models/columns
  • The macro must generate a SELECT that returns rows on failure
💡 Custom generic = write once, apply many times. Singular = write once for one specific model.
⚙️
Test Configuration Parameters
where · severity · warn_if · error_if · store_failures
Very High Frequency
ParameterWhat It Does
whereAdds a WHERE clause to the test query — limit which rows are tested (e.g., only last 7 days)
severitywarn or error — warn logs a warning but doesn't fail the run; error (default) fails the run
warn_ifThreshold expression for warning: ">=10" means warn if 10+ failures
error_ifThreshold expression for erroring: ">=100" means only error if 100+ failures
store_failuresCreates a table of failing rows in the warehouse for investigation
limitCaps the number of failure rows stored (with store_failures)
💡 The most exam-tested config: where for scoping incremental tests and store_failures for production debugging. Know both cold.
📡
Testing Sources
Validating raw data before it enters your pipeline
Medium Frequency
  • Tests can be applied to sources in sources.yml just like models in schema.yml
  • Same generic and custom tests apply to source columns
  • Test sources with dbt test --select source:source_name
  • Source freshness is separate from source tests — it checks if source data was loaded recently
  • Testing sources early catches data quality issues before they propagate into transformed models
  • dbt build runs source tests as part of the full build graph in DAG order
💡 Testing sources = shift-left quality. Catch bad source data before it corrupts 10 downstream models.

Domain 4 Study Checklist

Progress: 0 / 12 complete
Concept

Know all 4 built-in generic tests and what they validate

unique, not_null, accepted_values (values: list), relationships (to: ref/source, field:)

Practice

Add all 4 generic tests to a model in your practice project

Write them in schema.yml under the model's columns section

Concept

Understand singular tests — what they are and where they live

SQL files in tests/ that return rows on failure; use ref() and source()

Practice

Write a singular test for a business rule

Example: assert that total_amount is never negative

Concept

Understand custom generic tests — macro structure and when to use

Defined as macros, take model + column_name, useful for reusable logic

Exam Prep

Know the where config — scope tests to a date range or condition

Use to limit testing on large incremental models without scanning all rows

Exam Prep

Know store_failures — creates a table of failing rows

Useful for debugging test failures in production; combine with severity: warn

Concept

Understand severity, warn_if, and error_if thresholds

warn = log only; error = fail the run; warn_if/error_if set numeric thresholds

Practice

Apply tests to a source declaration in sources.yml

Add not_null and unique to source columns; run dbt test --select source:name

Exam Prep

Know the dbt test command flags: --select, --exclude

dbt test --select model:stg_* runs tests on all staging models

Concept

Understand how dbt build differs from dbt test in execution order

dbt build runs model + its tests before moving to downstream models; dbt test runs all tests after all models

Exam Prep

Read the Advanced Testing course on learn.getdbt.com

Covers custom generic tests, store_failures, and test configs in depth

Quick Reference

Generic Tests in schema.yml

models: - name: fct_orders columns: - name: order_id tests: - unique - not_null - name: status tests: - accepted_values: values: ['placed','shipped','returned'] - name: customer_id tests: - relationships: to: ref('dim_customers') field: customer_id

Test Config Parameters

- not_null: config: # Only test recent data where: "created_at > current_date - 7" # Warn but don't fail severity: warn # Store failures for debugging store_failures: true # Threshold-based severity warn_if: ">=1" error_if: ">=50"

Singular Test (tests/assert_revenue.sql)

-- Returns rows on FAILURE -- 0 rows = test passes SELECT order_id, total_amount FROM {{ ref('fct_orders') }} WHERE total_amount < 0

Custom Generic Test Macro

-- macros/test_not_negative.sql {% macro test_not_negative( model, column_name ) %} SELECT * FROM {{ model }} WHERE {{ column_name }} < 0 {% endmacro %} # Apply in .yml: tests: - not_negative

Testing Sources

sources: - name: salesforce tables: - name: accounts columns: - name: account_id tests: - unique - not_null # Run source tests only: dbt test --select source:salesforce

Test Commands

dbt test # all tests dbt test --select model_name # one model dbt test --select source:name # source tests dbt test --select tag:daily # tagged tests dbt build # run + test together

Domain 4 Practice Quiz

5 exam-style questions on dbt testing.

Domain 4 Study Plan

Days 1–2 · Generic Tests & Sources

  • Apply all 4 generic tests to models in your practice project
  • Add tests to a source declaration in sources.yml
  • Run dbt test --select source:source_name and review the output

Days 3–4 · Singular & Custom Generic Tests

  • Write 2 singular tests for business rules in your project
  • Write one custom generic test macro and apply it to 3 columns
  • Verify it compiles correctly with dbt compile --select test_name

Days 5–6 · Test Configs & Exam Prep

  • Add where, severity, and store_failures configs to existing tests
  • Intentionally fail a test with store_failures:true and inspect the failure table in the warehouse
  • Complete the Advanced Testing course on learn.getdbt.com
  • Do 15 practice questions on test configurations and test types

Common Exam Mistakes — Domain 4

1

Thinking singular tests PASS when they return rows

The logic is inverted from what you'd expect

What Goes Wrong

Candidates write singular tests that return "good" rows expecting them to pass. dbt fails a test when it returns ANY rows — zero rows = pass, any rows = fail.

The Fix

Write singular tests to SELECT the FAILING rows. If you want "assert no negative amounts", write: SELECT * FROM model WHERE amount < 0. Passing = no rows returned.

🛡️ Mental model: singular test = "find me the bad rows". If there are none, the test passes.
2

Confusing severity: warn with suppressing failures

warn still logs the failure — it doesn't hide it

What Goes Wrong

Candidates set severity: warn thinking the test won't show up as a failure. The test still runs and logs a warning — it just doesn't cause dbt run to exit with an error code.

The Fix

Use severity: warn for non-critical quality checks where you want visibility without blocking deployment. The warning is still logged and visible in dbt Cloud.

🛡️ warn = "flag this but don't block the pipeline". error (default) = "stop if this fails".
3

Using singular tests for logic that should be a custom generic

Duplication instead of reuse

What Goes Wrong

The same test SQL is copy-pasted into multiple singular test files, creating maintenance burden. Changes to the logic must be applied in multiple files.

The Fix

When the same test logic needs to apply to multiple models/columns, create a custom generic test macro instead. Apply it in .yml like any generic test.

🛡️ Rule: if you're copying a test SQL for the second time, make it a custom generic test.
4

Not using the where config on incremental model tests

Testing 3 years of data when you only need to test today's rows

What Goes Wrong

Running not_null or unique on a 500M-row incremental table scans the entire table on every run, making test runs extremely slow and expensive.

The Fix

Add a where config to scope the test to recent data: where: "created_at >= current_date - 7". This tests only the rows that the incremental model would have processed.

🛡️ For incremental models, always ask: "Do I really need to test ALL rows?" Usually you only need to validate the latest data window.
5

Forgetting that dbt build ≠ dbt run + dbt test

Order of operations matters for catching failures early

What Goes Wrong

dbt run builds all models, then dbt test runs all tests — if a test on stg_orders fails, fct_orders was already built with bad data. This wastes compute and propagates errors downstream.

The Fix

Use dbt build — it runs each node's model + tests before moving downstream. A test failure on stg_orders prevents fct_orders from building, stopping error propagation immediately.

🛡️ Production jobs should use dbt build, not dbt run && dbt test. The order-awareness of build prevents bad data from propagating.

Frequently Asked Questions

What is store_failures and when should I use it? +
store_failures: true causes dbt to create a table in your warehouse containing the rows that failed the test. This is invaluable for debugging production test failures — instead of just knowing "50 rows failed the not_null test", you can query the failure table to see exactly which rows are null and why. Enable it on high-value tests in production environments.
Can you apply test configs globally in dbt_project.yml? +
Yes — you can set test configs like store_failures and severity at the project level in dbt_project.yml under a tests: block. These apply to all tests unless overridden at the model or column level. This is useful for enabling store_failures in production without editing every test definition.
What is the difference between warn_if and error_if? +
warn_if and error_if set numeric thresholds for different severity levels. For example: warn_if: ">=1" and error_if: ">=50" means: if 1–49 rows fail → warn; if 50+ rows fail → error. This allows gradual escalation rather than a binary pass/fail.
Can a custom generic test accept parameters beyond model and column_name? +
Yes — custom generic test macros can accept any number of additional parameters. For example, a test that checks if a value is between a min and max would accept min_value and max_value parameters. These are passed in the .yml test definition: - between_values: {min_value: 0, max_value: 100}.
How do you run tests for a specific tag? +
Use dbt test --select tag:tag_name. Tags can be applied to tests in the same way as models — in the test's config block or in dbt_project.yml. This is useful for running a subset of expensive tests separately from routine tests.
Does dbt test run source tests and model tests the same way? +
Yes — both are run with dbt test. Source tests defined in sources.yml are picked up alongside model tests from schema.yml. You can filter with --select source:source_name to run only source tests, or --select model_name for model tests. Both generate SQL queries that must return 0 rows to pass.
Official Resources

Domain 4 Study Resources

Advanced testing course and documentation