DBT Contracts vs Data Tests on Snowflake: Ensuring Data Quality and Reliability
The views I express here are mine alone and do not necessarily reflect the opinions or official position of my employer.
Data quality and integrity is very important in the field of data engineering. When working with DBT (Data Build Tool) on Snowflake, both contracts and data tests are essential for ensuring data quality and reliability. However, they serve different purposes and are implemented differently and there are several caveats that needs to be picked up.
DBT Contracts
DBT Contracts are a feature that allows you to enforce the schema (columns, data types, and nullability) of your models. When a contract is enabled, DBT will validate that the model's output matches the declared schema before materializing the table or view in Snowflake.
Key Features
- Schema enforcement: Ensures columns, data types, and nullability match the contract.
- Early error detection: Fails the build if the schema does not match, preventing bad data from being loaded.
- Documentation: Contracts serve as living documentation for your model's expected output.
Example
models:
- name: customers
config:
contract:
enforced: true
columns:
- name: id
data_type: integer
constraints:
not_null: true
- name: email
data_type: string
constraints:
not_null: true
When to Use
- When you want to guarantee the structure of your data models.
- When downstream systems depend on a fixed schema.
DBT Data Tests
DBT Data Tests are SQL queries that check the validity of your data. They are used to assert business logic or data quality rules, such as uniqueness, referential integrity, or value ranges.
Key Features
- Custom logic: Write tests for any business rule using SQL.
- Row-level validation: Identify specific records that violate rules.
- Test types: Built-in (e.g.,
unique,not_null) and custom tests.
Example
-- tests/not_null_email.sql
SELECT *
FROM {{ ref('customers') }}
WHERE email IS NULL
models:
- name: customers
data_tests:
- not_null:
column_name: email
- unique:
column_name: id
When to Use
- To validate data quality and business rules.
- To catch data issues that schema contracts cannot enforce (e.g., value ranges, referential integrity), and because Snowflake constraints are only applied at DDL time and do not guarantee underlying data quality, therefore for ongoing validation you should add explicit data tests (e.g., dbt generic/singular tests) in your pipeline.
Comparison Table
| Feature | DBT Contract | DBT Data Test |
|---|---|---|
| Purpose | Schema enforcement | Data quality validation |
| Enforced by | DBT at build time | DBT at test time |
| Error Type | Build fails on mismatch | Test fails, build can pass |
| Use Case | Structure & type guarantee | Business logic & data rules |
| Example | Data types, nullability | Uniqueness, value ranges |
Summary
- Use contracts to enforce the structure and data_type of your models in Snowflake.
- Use data tests to validate the quality and correctness of your data.
- Both are complementary and should be used together for robust data pipelines.
Airflow Example: Where Contracts and Data Tests Stop
Consider an Airflow DAG that orchestrates your DBT workflow with two main tasks: dbt run and dbt test.
- DBT Contract: If a contract is violated (e.g., the model output doesn't match the declared schema), the failure occurs during the
dbt runstep. Airflow will mark thedbt runtask as failed, and downstream tasks (includingdbt test) will not execute. - DBT Data Test: If the contract passes but a data test fails (e.g., a uniqueness test fails), the failure happens during the
dbt teststep. Airflow will mark thedbt testtask as failed, but thedbt runtask will have succeeded.
Summary:
- Contracts stop the pipeline at the build/run phase if the schema is incorrect.
- Data tests stop the pipeline at the test phase if data quality rules are violated.
This separation allows you to catch structural issues early and business logic issues after data is loaded.