Tests are sleeping: replace time.sleep
Fixed time.sleep and asyncio.sleep calls make pytest suites slow and flaky. Poll for the condition, patch the sleep in retry and backoff code, and move the clock with time-machine.
A time.sleep(2) in a test does nothing for two seconds. It's there because the test waits for something: a background thread to finish, a cache entry to expire, a retry to happen. The sleep is a guess at how long that takes, and the guess has to be pessimistic, so the test almost always waits longer than it needs to.
Sleeps add up quickly. Ten tests with a one-second sleep cost ten seconds on every run, and sleeps hidden in application code (retry loops, backoff, rate limiters) run in every test that reaches them. They're also a classic source of flaky tests: a sleep long enough on your laptop can be too short on a busy CI runner.
Where the sleeps come from
- Waiting for a side effect:
start_worker(); time.sleep(1); assert job.done. - Retry and backoff code under test: an HTTP client that retries three times with 1, 2 and 4 second delays spends 7 seconds in a test that checks the failure path.
- Waiting for time to pass: a token that expires after 1 second, a cache with a TTL, a "created more than 5 minutes ago" query.
asyncio.sleep()in async tests and code, for the same reasons.- Fixed waits in browser tests, like Playwright's
page.wait_for_timeout().
Each needs a different fix.
Poll for the condition instead
If the test waits for something to happen, check for it in a loop with a short interval and a timeout. The test continues as soon as the condition is true, and the timeout only matters when the test is going to fail anyway:
import time
def wait_until(condition, timeout=5.0, interval=0.01):
deadline = time.monotonic() + timeout
while not condition():
if time.monotonic() >= deadline:
raise AssertionError(f"condition not met within {timeout}s")
time.sleep(interval)
# Before: always waits a full second, and fails if the worker is slower
start_worker(queue)
time.sleep(1)
assert queue.empty()
# After: returns as soon as the queue is drained
start_worker(queue)
wait_until(queue.empty)
A generous timeout (5 or 10 seconds) costs nothing when the test passes, and it makes the test more reliable on slow machines than any fixed sleep.
Even better is to not poll at all: have the code signal completion (threading.Event, a future, a callback) and wait on that. See tests blocked on threads, queues and the event loop. In browser tests, use the framework's auto-waiting assertions (Playwright's expect(locator).to_be_visible()) instead of fixed timeouts; see end-to-end testing best practices.
Patch the sleep in retry and backoff code
When the code under test sleeps between retries, the delay is irrelevant to the test. Replace the sleep with a mock and assert how often the code retried:
from unittest import mock
import pytest
from myapp import payments
def test_gives_up_after_three_attempts():
with (
mock.patch("myapp.payments.time.sleep") as sleep,
mock.patch("myapp.payments.send", side_effect=ConnectionError) as send,
):
with pytest.raises(payments.PaymentFailed):
payments.charge(order_id=1)
assert send.call_count == 3
assert sleep.call_args_list == [mock.call(1), mock.call(2)]
mock.patch("myapp.payments.time.sleep") patches the attribute on the time module itself, so every time.sleep call during the with block is mocked. If the module does from time import sleep, patch myapp.payments.sleep instead.
A cleaner long-term fix is to make the delay configurable and set it to zero in test settings (RETRY_BACKOFF = 0), or to pass it in as an argument. With tenacity, override the wait strategy for one call with retry_with:
from tenacity import wait_none
payments.charge.retry_with(wait=wait_none())(order_id=1)
For async code, patch asyncio.sleep where the module uses it, with mock.AsyncMock.
Move the clock instead of waiting for it
If the test waits for time to pass (expiry, TTLs, "older than" logic), move the clock with time-machine:
import datetime as dt
from myapp.tokens import create_token, is_valid
def test_token_expires(time_machine):
time_machine.move_to(dt.datetime(2026, 1, 1, 12, 0, tzinfo=dt.UTC), tick=False)
token = create_token(ttl=dt.timedelta(minutes=5))
assert is_valid(token)
time_machine.shift(dt.timedelta(minutes=6))
assert not is_valid(token)
The test covers a six-minute window in microseconds. time-machine mocks wall-clock functions like time.time() and datetime.now(), but not time.monotonic(), so code that measures timeouts with the monotonic clock still needs a configurable timeout or an injected clock. freezegun also moves the clock (and does freeze time.monotonic()), but it's slow to start; see replace freezegun with time-machine.
Keep the tests correct
- Polling keeps the test meaningful: it still checks that the thing happens, just without the fixed wait. Keep the timeout well above the normal duration so slow CI machines don't fail.
- A mocked sleep in retry code should still assert how often the code retried (and ideally with which delays). Otherwise a bug that retries forever, or never, goes unnoticed.
- When you set a delay to zero in test settings, keep one test that checks the real backoff schedule, with the sleep mocked and its arguments asserted.
How flakytest finds it
flakytest puts samples inside time.sleep() and asyncio.sleep() (any line that calls sleep(, in your code or a library's) in the sleep category. When sleeping takes more than 0.2 seconds and 1% of test time, it suggests Tests are sleeping, with the tests that sleep the most and the lines of your code that lead to the sleep. That tells you whether the sleep is in the test itself or deep in a retry helper. After the fix, flakytest compare shows how much faster the affected tests got.
Further reading
Related guides
- Tests blocked on threads, queues and the event loop: Tests that wait in Thread.join, Event.wait, Queue.get or select() burn time doing nothing. Lower timeouts in tests, signal completion, and run background work synchronously.
- Replace freezegun with time-machine: freezegun scans every loaded module each time it freezes time, which gets slow in big projects. time-machine mocks time at the C level. How to migrate, including tick=False.
- Common causes of flaky tests and how to fix each: Why pytest tests pass and fail on the same code: test order, time zones, randomness, sleeps, unordered results, xdist and slow CI. The symptom and fix for each.
Updated October 18, 2026 · Suggest a change