## TL;DR
You can run a whole DAG locally with `airflow dags test`, which executes tasks in dependency order in a single process with no scheduler and no executor. Pair it with `airflow tasks test` for single tasks and `airflow dags list-import-errors` for parse failures. These catch the vast majority of DAG bugs, including template rendering and logic errors, before anything touches production.

```text
how to test an Airflow DAG without running it
```

## Use this when
- You changed a DAG and want to validate it before committing
- A DAG fails in production but the code looks fine
- You want to check template rendering for a specific logical date

## Not for this skill when
- You need to test scheduler behavior (pool starvation, executor quirks, timing)
- You want load or concurrency testing of workers
- The test must run against production data or connections

## Steps

1. Check for import and parse errors first, since a broken file never even becomes a DAG:

```shell
airflow dags list-import-errors
```
Expected output: an empty list, or the filename plus traceback of the broken file. Fix those before anything else.

2. Run the whole DAG end to end in one process with a fixed logical date:

```shell
airflow dags test my_dag 2026-10-01
```
Expected output: tasks execute sequentially in dependency order, logs stream to your console, and the run completes without a scheduler, executor, or webserver involved.

3. Test a single task in isolation when you only changed one piece:

```shell
airflow tasks test my_dag my_task 2026-10-01 --task-params '{"key": "value"}'
```
Expected output: just that task runs, with rendered templates visible in the logs. Fast iteration loop for template and logic bugs.

4. Verify the DAG structure matches what you intended:

```shell
airflow dags show my_dag
```
Expected output: a graph representation of tasks and dependencies. Catches misplaced dependency arrows and orphaned tasks that unit tests miss.

5. For repeatable CI, add a pytest that parses the DAG and asserts on its shape:

```python
from airflow.models import DagBag

def test_dag_loads():
    bag = DagBag(dag_folder="[dag folder]", include_examples=False)
    assert not bag.import_errors
    dag = bag.get_dag("my_dag")
    assert dag is not None
    assert set(dag.task_ids) == {"extract", "transform", "load"}
```
Expected output: the test passes in CI, so broken DAGs never reach the scheduler in the first place.

6. If a task needs a connection or variable, stub it for the local run rather than pointing at production:

```shell
AIRFLOW_CONN_MY_DB='{"conn_type": "postgres", "host": "[staging host]"}' airflow dags test my_dag 2026-10-01
```
Expected output: the test run uses the stubbed connection. Local tests should never touch production systems.

## Variant phrasings

### test airflow dag locally without scheduler
That is exactly what `airflow dags test` is for. It is the closest thing to a dry run Airflow offers, running real operator code with real template rendering.

### airflow dags test vs tasks test
`dags test` runs the full DAG in order, including XCom passing between tasks in the same run. `tasks test` runs one task alone, so XCom pulls from other tasks will come back empty unless you seed them.

### how do I pass a logical date to a DAG test
Both commands take the date as a positional argument, like `airflow dags test my_dag 2026-10-01`. Templates using `ds` or `data_interval_start` render against that date.

## Why it happens
People fear testing DAGs because the scheduler feels like required infrastructure, but the scheduler only handles triggering and queuing. The actual task logic, template rendering, and dependency ordering all run fine in a single local process, which is what `dags test` does. Most production DAG failures are logic or rendering bugs, not scheduler bugs, so local testing catches them.

## Edge cases
- Sensors in poke mode will actually wait during a test run; use reschedule mode or mark them skipped for local testing.
- `dags test` ignores pools, so pool exhaustion bugs wont reproduce locally.
- Retries still apply, so a flaky task can make the test slow; temporarily lower retries when iterating.
- Environment variables and Airflow config must match the deployed environment, or you will test a different DAG than the one that runs.
- XCom works within a single `dags test` run, but values dont persist to the metadata DB, so the UI wont show them.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_IHpIGcL9LQswdns8FBsEQQ
