Data Engineering  /  dbt

🔄 dbt — Data Build Tool 60 guides · updated 2026

Analytics engineering with SQL — models, tests, sources, and Jinja macros that turn raw warehouse tables into trustworthy, documented data products.

dbt Source Testing: Validating Raw Data Before It Enters Your Pipeline

Most teams test their models thoroughly and never think to test their sources at all — which is backwards, given that every model built downstream inherits whatever problems exist in the raw data. A duplicate key in a source table doesn’t stay contained; it propagates through every join and aggregation that touches it. Testing sources directly catches upstream data problems at the earliest point they can be caught, before your own transformation logic has had a chance to amplify them.


Tests on Sources Look Exactly Like Tests on Models

The same generic tests covered in Generic Testsunique, not_null, accepted_values, relationships — attach to source columns in sources.yml with identical syntax to a model’s schema file.

sources:
- name: raw
schema: raw_app_data
tables:
- name: orders
columns:
- name: id
tests:
- unique
- not_null
- name: status
tests:
- accepted_values:
values: ['pending', 'shipped', 'delivered', 'cancelled']
- name: customers
columns:
- name: id
tests:
- unique
- not_null
- name: email
tests:
- not_null

Running dbt test --select source:raw executes only these source-level tests, isolated from model tests — useful as a fast, early check in a pipeline before any transformation work begins.


Why This Matters More Than It Looks Like It Should

Consider a raw orders table with a duplicate id — perhaps a loader retried a failed sync and inserted the same batch twice. Without a source test, that duplicate flows silently into your staging model, then into every join downstream. Revenue gets double-counted. Order counts get inflated. By the time someone notices the dashboard numbers look wrong, you’re debugging four layers away from where the actual problem originated.

A unique test directly on the source catches this in seconds, at the exact point the bad data entered the project — before a single downstream model has run.


Combining Source Freshness With Source Tests

Source testing and source freshness (covered in Source Freshness) are complementary, not the same thing. Freshness asks “is this data recent enough?” Testing asks “is this data structurally correct?” A production pipeline benefits from running both, typically freshness first:

Terminal window
dbt source freshness
dbt test --select source:*
dbt build

If a source is stale, there’s often little point running structural tests on it yet — but both checks running before the main build means you catch upstream problems before spending compute transforming data you already know is compromised.


Testing Relationships Between Sources

relationships tests aren’t limited to checking a model column against another model — they work equally well checking one source against another, catching sync inconsistencies between systems that are supposed to stay aligned.

sources:
- name: raw
tables:
- name: order_items
columns:
- name: order_id
tests:
- relationships:
to: source('raw', 'orders')
field: id

This catches a specific and common failure mode in multi-table syncs: order_items finished loading before orders did, or a partial sync dropped some parent records, leaving orphaned line items that reference orders which technically don’t exist yet in your raw layer.


A Practical Source Testing Checklist

For any new source, a reasonable minimum bar before building anything downstream of it:

CheckWhy
unique + not_null on the primary keyCatches duplicate/missing rows at the earliest layer
not_null on foreign keysEnsures joins downstream won’t silently drop unmatched rows
accepted_values on status/category columnsCatches new enum values before they break downstream logic
freshness configEnsures the source is actually current before building on it

Source Tests in CI

Because source tests run against the same physical raw tables regardless of which dbt environment triggered them, they’re a natural fit for CI pipelines that want a fast early signal — testing sources takes seconds and requires no model builds at all, making it a cheap first gate before running the (often much more expensive) full model build and test suite.

# Example CI step ordering (pseudocode)
- run: dbt deps
- run: dbt source freshness
- run: dbt test --select source:*
- run: dbt build

A failure at the source-testing stage stops the pipeline before any compute is spent transforming data that’s already known to be broken — a meaningful cost and time saving on large projects.


Common Mistakes

Only testing models, never sources. This is the most common gap — teams invest heavily in model-level tests while raw tables go completely unvalidated, missing the earliest and cheapest point to catch a problem.

Assuming the loading tool guarantees data quality. ELT tools like Fivetran or Airbyte move data reliably, but they don’t validate business rules or guarantee uniqueness at the destination — that’s still dbt’s job, applied to the source layer.

Not testing relationships across sources from different systems. Sync timing differences between separately-loaded systems are a common and often-overlooked source of orphaned records that only a cross-source relationships test will catch.

Summary

PracticeBenefit
Generic tests on source columnsCatches structural problems in raw data immediately
relationships across sourcesCatches sync timing issues between separately loaded systems
Testing sources before building modelsFails fast and cheap, before compute is spent on bad data
Combining with freshness checksEnsures data is both current and structurally correct

Testing your sources is the cheapest, earliest insurance policy in a dbt project — catching a problem here costs a few seconds of query time; catching the same problem four layers downstream, after it’s been joined and aggregated repeatedly, costs a debugging session and a correction to every affected dashboard.