dbt Generic Tests

Generic tests are the most common way to add data quality checks in dbt. You declare them in YAML files alongside your model definitions. dbt generates the SQL automatically and runs it when you execute dbt test. This topic covers the four built-in generic tests in depth and shows you how to apply them across different scenarios.

How Generic Tests Work

When you add a generic test in YAML, dbt looks up a corresponding SQL template and fills in the column name and any parameters. The test runs as a SQL query. If the query returns zero rows, the test passes. If it returns any rows, those rows represent failures.

You write (YAML):
  - name: order_id
    tests:
      - unique

dbt generates (SQL):
  select order_id
  from dbt_dev.stg_orders
  where order_id is not null
  group by order_id
  having count(*) > 1

The generated SQL returns only duplicate order IDs. If none exist, zero rows come back and the test passes.

not_null

The not_null test verifies that a column contains no NULL values. Apply it to any column that must always have a value, such as primary keys, foreign keys, and mandatory business fields.

- name: stg_orders
  columns:
    - name: order_id
      tests:
        - not_null
    - name: customer_id
      tests:
        - not_null
    - name: order_date
      tests:
        - not_null

Generated SQL for not_null

select count(*) as failures
from dbt_dev.stg_orders
where order_id is null

unique

The unique test verifies that every value in a column appears only once. Apply it to primary key columns. Duplicates in a primary key column cause downstream joins to produce extra rows, which inflates metrics silently.

- name: stg_customers
  columns:
    - name: customer_id
      tests:
        - not_null
        - unique

When Primary Keys Can Duplicate

Scenario: stg_customers is built from two source systems merged with UNION ALL
Problem:  Customer 42 appears in both systems, so two rows with customer_id=42 exist
Result:   unique test fails, alerting you to the duplicate before it causes join inflation

accepted_values

The accepted_values test verifies that a column only contains values from a predefined list. Apply it to status columns, category columns, and any field with a finite set of valid options.

- name: stg_orders
  columns:
    - name: status
      tests:
        - accepted_values:
            values: ['pending', 'processing', 'completed', 'cancelled', 'refunded']

- name: stg_products
  columns:
    - name: category
      tests:
        - accepted_values:
            values: ['electronics', 'clothing', 'food', 'books', 'home']

Handling Case Sensitivity

By default, the accepted_values test is case-sensitive. If your data contains both Completed and completed, both must appear in the values list, or you must clean the column in your staging model using lower(status) before testing.

-- Cleaning in the model before testing:
select lower(status) as status from {{ source('ecommerce', 'orders') }}

-- Then test expects only lowercase:
- accepted_values:
    values: ['pending', 'completed', 'cancelled']

relationships

The relationships test verifies referential integrity between two tables. Every value in the column being tested must exist in the referenced column of another table.

- name: stg_orders
  columns:
    - name: customer_id
      tests:
        - relationships:
            to: ref('stg_customers')
            field: customer_id

    - name: product_id
      tests:
        - relationships:
            to: ref('stg_products')
            field: product_id

Why Referential Integrity Fails

Common causes of relationships test failures:
  1. Customer deleted from CRM but old orders still reference their ID
  2. Two systems synced at different times (orders arrived before customers)
  3. Test/sandbox records in production data
  4. Data type mismatch (integer in orders, string in customers)

Applying Multiple Tests to One Column

- name: order_id
  tests:
    - not_null          ← must have a value
    - unique            ← must not repeat
- name: customer_id
  tests:
    - not_null          ← every order must have a customer
    - relationships:    ← that customer must exist
        to: ref('stg_customers')
        field: customer_id

Applying Tests at Model Level

Some tests apply to combinations of columns rather than single columns. Define these at the model level using the tests key directly under the model name:

- name: stg_order_items
  tests:
    - unique_combination_of_columns:        ← from dbt_utils package
        combination_of_columns:
          - order_id
          - product_id
  columns:
    - name: order_id
      tests:
        - not_null

Configuring Test Behavior

Severity

- name: email
  tests:
    - not_null:
        severity: warn     ← pipeline continues even if this fails

Error if Count Exceeds Threshold

- name: status
  tests:
    - accepted_values:
        values: ['pending', 'completed', 'cancelled']
        config:
          error_if: ">=10"   ← pass if fewer than 10 failures
          warn_if: ">=1"    ← warn if any failures but fewer than 10

Running Specific Tests

# Run all tests on one model
dbt test --select stg_orders

# Run only not_null tests across entire project
dbt test --select test_type:not_null

# Run only the unique test on a specific column
dbt test --select "stg_orders,test_type:unique"

Practical Testing Strategy

Column Type        Recommended Tests
-----------        -----------------
Primary key        not_null + unique
Foreign key        not_null + relationships
Status field       accepted_values
Required field     not_null
Calculated metric  Custom singular test (Topic 16)

Apply not_null and unique to every primary key column in every model. This one habit catches the majority of data quality issues in most projects.

Leave a Comment

Your email address will not be published. Required fields are marked *