05 / 05

How would you test asynchronous code written with asyncio?

Difficulty: 8/10
Asyncio Testing, Pytest Asyncio, Async Mock, Cancellation

Use pytest-asyncio or IsolatedAsyncioTestCase to run coroutines, fake slow dependencies, and control the clock

Async code needs an event loop to run, so a plain test function cannot just await. The modern options are pytest-asyncio with @pytest.mark.asyncio, which gives you an async test function and a loop per test, or unittest.IsolatedAsyncioTestCase if you are staying in the stdlib. Both give a fresh event loop and clean up pending tasks, which matters because leftover tasks across tests cause flakiness. For dependencies, use AsyncMock to fake coroutine collaborators and assert with assert_awaited_once_with. For timing, avoid real sleeps: patch asyncio.sleep or use a library like aioresponses or a faker clock, because real sleeps make the suite slow and nondeterministic. Test cancellation and timeouts explicitly, because those paths are the ones that break in production.

  1. 1

    pytest-asyncio with @pytest.mark.asyncio or asyncio_mode = auto is the common setup.

  2. 2

    unittest.IsolatedAsyncioTestCase is the stdlib option and works with the standard runner.

  3. 3

    AsyncMock (3.8+) is required to mock coroutine functions. A regular Mock returns a coroutine that is never awaited and the test will warn or fail.

  4. 4

    Use asyncio.wait_for to test timeouts, and assert on asyncio.TimeoutError or TimeoutError in 3.11+.

  5. 5

    Test task cancellation by cancelling and asserting on the cleanup path.

  6. 6

    Trade-off: loop-per-test is more isolated but slower than a shared loop. Loop-per-test is the safer default.

  7. 7

    Common mistake: leaving background tasks running after the test, which leaks into other tests and causes flaky failures.

  8. 8

    Common mistake: patching time.sleep when the code awaits asyncio.sleep. Patch the right function.

  9. 9

    Version note: asyncio.TimeoutError was unified with built-in TimeoutError in 3.11, which simplifies exception assertions.

Scenario Questions

0-2 years experience

  1. 1Why can't a normal def test function await a coroutine?
  2. 2Which mock class do you use to replace an async function?

2-5 years experience

  1. 1Your async test sometimes fails because a background task keeps running after the test ends. How do you fix it?
  2. 2You want to test a timeout path without waiting the real timeout. How do you do it?

5-8 years experience

  1. 1You need to test a client that fans out to three services with per-call timeouts and partial failures. How do you structure the test and the fakes?
  2. 2Your tests pass locally and fail in CI due to event loop policy differences. How do you make the loop configuration consistent?

8+ years experience

  1. 1Design a test suite for an async service that covers cancellation, back-pressure, and graceful shutdown without flakiness or leaked tasks.
  2. 2Explain how pytest-asyncio modes, loop fixtures, and async generators interact, and how to migrate a large suite safely.

Follow-up Questions

  • Why does a regular Mock fail when mocking a coroutine function?
  • How would you test that a coroutine cancels its children when the parent is cancelled?
Share

Share via WhatsApp, X, Facebook, LinkedIn or copy link. Open Graph preview enabled.