DEV Community

Mihir Shinde
Mihir Shinde

Posted on • Originally published at kleore.com

How to Find and Fix Flaky Tests in pytest

pytest is the gold standard for Python testing. But its powerful fixture system and Python's dynamic nature create unique flakiness patterns that only surface in CI.

This guide covers the five most common causes of flaky pytest tests and gives concrete fixes for each.

Why pytest tests become flaky

  • Database state leaking between tests — Tests share a DB and don't isolate transactions
  • Time-dependent assertions — Tests use datetime.now() directly
  • Network calls to real services — Real HTTP requests to external APIs
  • File system conflicts — Tests write to the same paths with pytest-xdist
  • Import side effects — Modules that execute code at import time

How to identify flaky pytest tests

pytest-randomly shuffles test order to surface ordering dependencies. pytest-repeat stress-tests a suspected flake by running it N times. pytest-rerunfailures automatically reruns failed tests — tests that pass on rerun are flaky by definition.

The 5 patterns and fixes

Pattern 1: Database state leaking

Use autouse=True fixtures that wrap every test in a transaction rollback.

@pytest.fixture(autouse=True)
def reset_db(db):
    yield
    db.session.rollback()
    db.session.remove()
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Time-dependent tests

Mock time with freezegun. Never let tests depend on the wall clock in CI.

from freezegun import freeze_time

@freeze_time("2026-01-15 10:00:00")
def test_token_expiry():
    token = create_token(expires_in=3600)
    assert not token.is_expired()
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Network calls

Mock HTTP with the responses library. Tests should never reach real external APIs.

import responses

@responses.activate
def test_fetch_user():
    responses.add(responses.GET, "https://api.example.com/user/1",
                  json={"id": 1, "name": "Alice"}, status=200)
    result = fetch_user(1)
    assert result["name"] == "Alice"
Enter fullscreen mode Exit fullscreen mode

Pattern 4: File system conflicts

Use pytest's tmp_path fixture for isolated per-test directories.

def test_write_report(tmp_path):
    report_file = tmp_path / "report.json"
    write_report(report_file)
    assert report_file.exists()
Enter fullscreen mode Exit fullscreen mode

Pattern 5: Import side effects

Patch module-level initialization and reset global state in fixture teardown.

@pytest.fixture(autouse=True)
def reset_global_config():
    original = config.copy()
    yield
    config.clear()
    config.update(original)
Enter fullscreen mode Exit fullscreen mode

CI configuration tips

Set PYTHONDONTWRITEBYTECODE=1 to prevent .pyc conflicts in parallel runs. Set PYTHONHASHSEED=0 for deterministic dictionary ordering.

Quarantining while you fix

pytest-quarantine marks known-flaky tests so they don't block CI. For automated detection, Kleore analyzes your GitHub Actions history and ranks every flaky test by failure rate and dollar cost.


Related: How to Fix Flaky Tests in GitHub Actions | How Much Do Flaky Tests Actually Cost?

Top comments (0)