# Testing Data Pipelines with pytest

> What to test in a pipeline, how to build tiny hand-checked fixtures, and the tests that catch real production bugs: replays, duplicates and bad rows.

- Source: https://akshaythakare.com/notes/python/testing-pipelines-with-pytest
- Topic: Python
- Level: intermediate
- Reading time: 2 min
- Tags: python, testing, pytest, data-quality, interview
- Updated: 2026-10-11

## Test behaviour, not lines of code

The bugs that hurt in production are rarely syntax errors. They are behaviours:

| Behaviour | Test |
| --- | --- |
| Re-running a batch | Output is identical (idempotent) |
| Duplicate source rows | Counted once |
| Bad rows | Rejected or quarantined, not silently dropped |
| Failure half-way | Nothing partial is published |
| Late data | Lands in the right partition |

## A fixture you can check by hand

```python
import pytest

@pytest.fixture
def orders():
    return [
        {"order_id": "O1", "qty": 2, "price": 500},   # 1000
        {"order_id": "O1", "qty": 2, "price": 500},   # duplicate resend
        {"order_id": "O2", "qty": 1, "price": 300},   # 300
    ]

def test_duplicates_counted_once(orders):
    assert total_sales(dedupe(orders)) == 1300
```

Keep fixtures small enough that the expected number fits in a comment. If you need a calculator to check a test, the fixture is too big.

## Test idempotency directly

```python
def test_replay_changes_nothing(tmp_path):
    run_pipeline(tmp_path, batch="2026-01-05")
    first = read_facts(tmp_path)
    run_pipeline(tmp_path, batch="2026-01-05")
    assert read_facts(tmp_path) == first
```

`tmp_path` gives each test its own directory, so tests never share state.

## Inject failures

```python
def test_failure_rolls_back(tmp_path, monkeypatch):
    def boom(*args, **kwargs):
        raise RuntimeError("injected")
    monkeypatch.setattr(pipeline, "publish", boom)
    with pytest.raises(RuntimeError):
        run_pipeline(tmp_path, batch="2026-01-05")
    assert read_facts(tmp_path) == []
```

> **Tip: Interview framing**
>
> "I test the guarantees: replay, duplicates, bad rows and rollback, each with a fixture small enough to verify by hand."
