dbt Tests
A look into generic tests and how to create custom singular tests for robust data validation.
Testing is a core part of building reliable data models in dbt. This document explores:
- The four built-in generic tests
- How and when to use them
- Creating singular tests for custom logic
- Real-world examples and expected output
What Are Generic Tests in dbt?
Generic tests are pre-built reusable macros that you can apply to models, columns, or sources using simple YAML configurations.
dbt ships with 4 built-in generic tests:
| Test Name | Description |
|---|---|
not_null | Ensures no NULL values exist in the column |
unique | Ensures all values in the column are unique |
accepted_values | Ensures all column values are from a defined list |
relationships | Ensures referential integrity between two tables |
Using Generic Tests: Syntax and Examples
Example Model: dim_users
-- models/dim_users.sql
SELECT
id,
email,
status
FROM {{ ref('stg_users') }}YAML Configuration
# models/dim_users.yml
version: 2
models:
- name: dim_users
columns:
- name: id
tests:
- not_null
- unique
- name: status
tests:
- accepted_values:
values: ['active', 'inactive', 'banned']
- name: email
tests:
- not_nullBreakdown
not_null
Ensures column has no NULL values.
✅ Valid:
| id |
|---|
| 1 |
| 2 |
❌ Invalid:
| id |
|---|
| 1 |
| null |
unique
Ensures column values are distinct.
✅ Valid:
| id |
|---|
| 1 |
| 2 |
❌ Invalid:
| id |
|---|
| 1 |
| 1 |
accepted_values
Ensures all values are within the allowed list.
✅ Valid:
| status |
|---|
| active |
| banned |
❌ Invalid:
| status |
|---|
| paused |
| unknown |
relationships
Ensures a column's values exist in a referenced table.
- name: orders
columns:
- name: user_id
tests:
- relationships:
to: ref('dim_users')
field: idThis ensures every user_id in orders exists in dim_users.id.
What Are Singular Tests?
Singular tests are custom SQL queries that return failing rows. If any rows are returned, the test fails.
Directory and Naming
Place singular tests inside the tests/ folder.
tests/
└── test_users_have_email.sqlExample Test
-- tests/test_users_have_email.sql
SELECT *
FROM {{ ref('dim_users') }}
WHERE email IS NULLAdd to dbt_project.yml
If you follow standard naming, no special registration is required.
When to Use Generic vs. Singular
| Test Type | Use For | Advantage |
|---|---|---|
| Generic Test | Common checks (nulls, uniqueness) | Fast to write and automatically documented |
| Singular Test | Custom business logic or complex SQL | Fully customizable |
dbt Test Output
When you run dbt test, the output will include a summary:
$ dbt test
PASS tests.test_not_null_dim_users_id ....................... PASS in 0.13s
FAIL tests.test_users_have_email ............................ FAIL in 0.22s
Failure in test test_users_have_email (tests/test_users_have_email.sql)
Got 3 results, expected 0
Each failing test will list the violating rows.
Tips and Best Practices
- Add generic tests via YAML in the same file as your model
- Write singular tests for business rules (e.g. "users must have an email")
- Include relationships tests for foreign keys
- Use
--store-failuresflag to output failed rows to tables for inspection
Summary
| Feature | Generic Test | Singular Test |
|---|---|---|
| Usage | YAML | Raw SQL in tests/ |
| Customizable | Limited to parameters | Fully customizable logic |
| Output | Auto-generated failure summary | Returns rows on failure |
| Good For | Common patterns | Business rules or edge cases |
Run All Tests
To execute all configured tests:
dbt testTo run tests for a specific model:
dbt test --select dim_usersTo store failed test output as a table:
dbt test --store-failures