Async mocking
Chainmock provides comprehensive support for mocking and asserting async methods. You can mock async methods, set their return values, and verify how they were awaited.
Chainmock uses unittest AsyncMock under the hood to provide async mocking functionality. This means that you can use all the same features and functionality as with AsyncMock.
Note
Methods like called() also work with async functions. However, if you want to explicitly assert that a method was awaited, instead of just called, you should use the awaited() method.
Basic async mocking
To mock an async method, use the regular mock() function. Chainmock will automatically detect that the method is async and create an appropriate async mock:
async def mock_async_method():
mocker(Teapot).mock("timer").return_value("mocked")
assert await Teapot().timer(1, 2) == "mocked"
You can force a mock to be async by setting force_async=True, which is useful when working with stubs or when automatic detection doesn't work:
async def mock_forced_async():
stub = mocker()
stub.mock("async_method", force_async=True).return_value("forced async")
assert await stub.async_method() == "forced async"
Asserting await calls
Basic assertions
Assert that an async method was awaited at least once:
async def assert_awaited():
mocker(Teapot).mock("timer").awaited()
await Teapot().timer(5)
Assert that an async method was never awaited:
mocker(Teapot).mock("timer").not_awaited()
Advanced assertions
You can also assert the number of times an async method was awaited:
async def assert_awaited():
mocker(Teapot).mock("open").awaited_once()
mocker(Teapot).mock("close").awaited_twice()
Also more advanced call count assertions are supported:
async def assert_await_counts():
mock = mocker(Teapot)
# Exact count
mock.mock("timer").await_count(3)
# At least once
mock.mock("open").await_count_at_least(1)
# At most twice
mock.mock("close").await_count_at_most(2)
Asserting await arguments
Assert that the last await was with specific arguments:
async def assert_last_await():
mocker(Teapot).mock("timer").awaited_last_with(5, seconds=30)
await Teapot().timer(5, seconds=30)
Assert that any await included specific arguments:
async def assert_any_await():
mocker(Teapot).mock("timer").any_await_with(5, seconds=30)
await Teapot().timer(2) # This one doesn't match
await Teapot().timer(5, seconds=30) # This one does
Match partial arguments in awaits:
async def assert_partial_args():
# Match just the minutes parameter
mock = mocker(Teapot).mock("timer")
mock.match_args_any_await(5)
await Teapot().timer(5, seconds=30)
# Match just the seconds parameter
mock.match_args_any_await(seconds=45)
await Teapot().timer(3, seconds=45)
Assert a sequence of awaits:
from chainmock.mock import call
async def assert_await_sequence():
mock = mocker(Teapot).mock("timer")
mock.has_awaits([
call(5),
call(3, seconds=30)
])
await Teapot().timer(5)
await Teapot().timer(3, seconds=30)
Side effects and return values
You can use return_value() and side_effect() with async mocks just like with regular mocks:
async def mock_async_returns():
# Return a fixed value
mock = mocker(Teapot)
mock.mock("timer").return_value(42)
assert await Teapot().timer(5) == 42
# Return a sequence of values
mock.mock("open").side_effect([
"opened",
"already open",
Exception("stuck!")
])
assert await Teapot().open() == "opened"
assert await Teapot().open() == "already open"
try:
await Teapot().open()
except Exception as e:
assert str(e) == "stuck!"
Note
For more information, please see also API reference. It contains more examples and extensive documentation about every method and function available in Chainmock.