Skip to content

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.