Unit Testing Airflow DAGs
pytest & airflow.testing Utilities π§ͺπ οΈβ
The Story: The DAG That Failed After Deploymentβ
Your DAG passed code review.
It worked on your laptop.
It even ran once in staging.
Then it hit production β and failed instantly.
- Wrong schedule
- Missing task dependency
- Typo in task ID
- Broken import
All of these failures had one thing in common:
They could have been caught before deployment.
Thatβs why unit testing Airflow DAGs is a professional requirement, not an optional luxury.
This article explains how to unit test Airflow DAGs, what should and should not be tested, and how to integrate tests into CI/CD pipelines.
What Does βUnit Testing a DAGβ Mean?β
Unit testing Airflow DAGs does not mean running full pipelines.
Instead, it means validating:
- DAG loads without errors
- DAG has correct schedule and configuration
- Tasks exist and are correctly named
- Dependencies are correctly defined
- Task logic behaves as expected (when applicable)
π No scheduler, no executor, no production systems involved.
Why Unit Testing DAGs Is Criticalβ
Without tests, DAG issues are found:
β At runtime
β By the scheduler
β In production
With tests, DAG issues are found:
β
During development
β
In pull requests
β
Before deployment
What Should You Test in an Airflow DAG?β
| Area | Test Type |
|---|---|
| DAG loading | Import test |
| DAG properties | Schedule, start_date |
| Tasks | Existence & type |
| Dependencies | Upstream / downstream |
| Python logic | Function-level tests |
What You Should NOT Unit Testβ
β External systems (databases, APIs)
β Actual task execution in Airflow
β Scheduler behavior
β Performance and load
Unit tests validate structure and logic, not execution.
Testing Tools Youβll Useβ
Core Toolsβ
pytestairflow.testingDagBag
Project Structure for Testingβ
airflow_project/
βββ dags/
β βββ sales_etl.py
βββ tests/
βββ test_sales_etl_dag.py
π Tests live outside the dags/ folder.
Test 1: DAG Loads Without Errorsβ
This is the most important test.
from airflow.models import DagBag
def test_dag_loaded():
dag_bag = DagBag(dag_folder="dags/", include_examples=False)
assert len(dag_bag.import_errors) == 0
Inputβ
| DAG files | sales_etl.py |
Outputβ
| Result | Value |
|---|---|
| Import errors | 0 |
β If this fails, your DAG will not appear in the UI.
Test 2: DAG Exists and Has Correct IDβ
def test_dag_id():
dag_bag = DagBag()
dag = dag_bag.get_dag("sales_etl")
assert dag is not None
Inputβ
| Expected DAG ID | sales_etl |
Outputβ
| Result | Pass |
Test 3: Validate DAG Configurationβ
from datetime import datetime
def test_dag_schedule_and_start_date():
dag = DagBag().get_dag("sales_etl")
assert dag.schedule_interval == "@daily"
assert dag.start_date == datetime(2024, 1, 1)
Inputβ
| Property | Expected |
|---|---|
| Schedule | @daily |
| Start date | 2024-01-01 |
Outputβ
β Configuration matches expectation
Test 4: Validate Tasks Existβ
def test_tasks_exist():
dag = DagBag().get_dag("sales_etl")
task_ids = dag.task_ids
assert "extract_data" in task_ids
assert "transform_data" in task_ids
assert "load_data" in task_ids
Inputβ
| Expected Tasks |
|---|
| extract_data |
| transform_data |
| load_data |
Outputβ
| Missing tasks | None |
Test 5: Validate Task Dependenciesβ
def test_task_dependencies():
dag = DagBag().get_dag("sales_etl")
extract = dag.get_task("extract_data")
transform = dag.get_task("transform_data")
load = dag.get_task("load_data")
assert transform in extract.downstream_list
assert load in transform.downstream_list
Inputβ
| Dependency | Expected |
|---|---|
| extract β transform | True |
| transform β load | True |
Outputβ
β DAG flow is correct
Test 6: Unit Test Python Task Logicβ
Test Python logic outside of Airflow.
from dags.sales_etl import calculate_total
def test_calculate_total():
data = [10, 20, 30]
result = calculate_total(data)
assert result == 60
Inputβ
| Data | [10, 20, 30] |
Outputβ
| Result | 60 |
π This keeps tests fast and reliable.
airflow.testing Utilities (Advanced)β
Airflow provides helpers for DAG testing.
from airflow.testing.utils import assert_dag_loaded
def test_dag_with_airflow_utils():
assert_dag_loaded("sales_etl")
π These utilities simplify common validation patterns.
Running Tests Locallyβ
pytest tests/
Output Exampleβ
=================== test session starts ===================
collected 6 items
test_sales_etl_dag.py ...... [100%]
==================== 6 passed ============================
Unit Testing in CI/CD Pipelinesβ
Common CI flow:
Git Push
β Run pytest
β Validate DAGs
β Build image
β Deploy Airflow
π Broken DAGs never reach production.
Input & Output β Realistic Testing Scenarioβ
Inputβ
- 12 DAGs
- 80 tasks total
- 40 tests
Outputβ
| Result | Value |
|---|---|
| DAG import errors | 0 |
| Failed tests | 0 |
| Deployment | Allowed |
Common Testing Mistakesβ
β No DAG import test
β Testing external systems
β Hardcoding dates incorrectly
β Mixing unit and integration tests
β Skipping tests due to time pressure
Best Practices for Professional Teamsβ
β
Every DAG must have tests
β
Test DAG structure first
β
Test Python logic separately
β
Run tests in CI
β
Fail fast, deploy safely
Summary π§ β
- Unit testing prevents broken DAGs
- Test DAG loading, structure, and logic
- pytest + airflow.testing are sufficient
- Tests are fast and automation-friendly
- CI + tests = stable Airflow
Key Takeawaysβ
- β DAGs are code β test them
- β Import errors are critical failures
- β Dependencies must be validated
- β Python logic should be isolated
- β Testing saves production incidents
Whatβs Next? πβ
β‘οΈ DAG Validation & Linting Tools
(Using airflow dags test, airflow dags list, and flake8 to enforce quality before runtime)