pytest fixtures I keep copying between projects

2026-05-03 · 5 min read

Every project I touch ends up with the same four or five fixtures in conftest.py. Rather than copy them from the last repo again, here they are.

A workspace that cleans itself up

@pytest.fixture
def workspace(tmp_path, monkeypatch):
    monkeypatch.chdir(tmp_path)
    return tmp_path

Half of my tests write files. Changing into a temporary directory means a test can be sloppy about paths without leaving anything behind.

A clock I control

@pytest.fixture
def clock(monkeypatch):
    now = [1_700_000_000.0]

    def fake_time():
        return now[0]

    monkeypatch.setattr(time, "time", fake_time)
    monkeypatch.setattr(time, "sleep", lambda s: now.__setitem__(0, now[0] + s))
    return now

Retry loops and timeouts become instant and deterministic. Tests that used to take forty seconds now take milliseconds, and they no longer fail on a loaded CI runner.

Retrying a flaky call

def eventually(fn, timeout=10.0, interval=0.2):
    deadline = time.time() + timeout
    last = None
    while time.time() < deadline:
        try:
            return fn()
        except AssertionError as exc:
            last = exc
            time.sleep(interval)
    raise last

Not a fixture, but it lives in the same file. When a test waits for a state change somewhere else, this replaces a hopeful sleep(5) with an explicit deadline and a real error message.

Capturing logs as structured records

caplog already does this, but I always want a small helper that returns just the messages at a given level, so assertions read as English rather than as tuple indexing.