Async services need tests too, and plain TestCase cannot await.
unittest.IsolatedAsyncioTestCase (3.8+) runs each test in its own fresh event loop, so test methods can be async def and await freely.
class TestClient(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self): self.client = await connect()
async def test_get(self): self.assertEqual(await self.client.get("k"), 1)
async def asyncTearDown(self): await self.client.close()Lifecycle: setUp, then asyncSetUp, the test, asyncTearDown, tearDown. self.addAsyncCleanup(coro_fn) registers async cleanup, and enterAsyncContext(cm) (3.11) enters an async context manager. At the end the loop is shut down and remaining tasks are cancelled. Since 3.13 a custom loop factory can be set with the loop_factory class attribute.
`AsyncMock` is a mock whose call returns an awaitable. Its return_value is what the await produces, and it records awaits: assert_awaited_once_with(...), await_count. patch automatically uses AsyncMock when the target is an async function, and MagicMock configures async magic methods (__aenter__, __aiter__) too.
A common bug: calling an AsyncMock without awaiting it. The test may pass while nothing was really checked; assert_awaited_* catches that.
Example
import asyncio, unittest
from unittest.mock import AsyncMock
async def fetch_price(client, symbol):
data = await client.get(f"/price/{symbol}")
return round(data["price"] * 1.2, 2)
class TestPrice(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
self.client = AsyncMock()
self.client.get.return_value = {"price": 10.0}
async def test_adds_markup(self):
self.assertEqual(await fetch_price(self.client, "ABC"), 12.0)
self.client.get.assert_awaited_once_with("/price/ABC")
async def test_timeout(self):
self.client.get.side_effect = TimeoutError
with self.assertRaises(TimeoutError):
await asyncio.wait_for(fetch_price(self.client, "X"), 1)
unittest.main(exit=False) # Ran 2 tests ... OKRemember
- IsolatedAsyncioTestCase gives each test its own event loop
- Use asyncSetUp, asyncTearDown and addAsyncCleanup for async fixtures
- AsyncMock returns awaitables; check with assert_awaited_once_with
Watch out for
- Calling an AsyncMock without await and asserting assert_called; use assert_awaited to ensure it was awaited.