1

The below example class has a property bar that is awaitable, as in async_main() because it (theoretically) does some IO work before returning the answer to everything.

class Foo:

    @property
    async def bar(self):
        return 42


async def async_main():
    f = Foo()
    print(await f.bar)

I'm having trouble testing it, as the usual suspects of Mock, MagicMock, and AsyncMock don't work with properties as expected. My current workaround is:

f.bar = some_awaitable()

since this makes f.bar a 'field' that can be awaited, but unfortunately I need to access it multiple times while it's under test, which yields RuntimeError: cannot reuse already awaited coroutine on the second access of course.

Is there an established way to mock an async property like this?

Brendano257
  • 553
  • 5
  • 10

1 Answers1

2

The easiest way that I can think of is to patch bar again with an async property for the purposes of your test.

I am assuming you have some other method on Foo that you want to test, and that method calls its bar.


code.py

from asyncio import run


class Foo:
    @property
    async def bar(self) -> int:
        return 42

    async def func(self) -> int:
        return await self.bar


async def main():
    f = Foo()
    print(await f.func())


if __name__ == '__main__':
    run(main())

test.py

from unittest import IsolatedAsyncioTestCase
from unittest.mock import patch

from . import code


class FooTestCase(IsolatedAsyncioTestCase):
    async def test_func(self) -> None:
        expected_output = 69420

        @property
        async def mock_bar(_foo_self: code.Foo) -> int:
            return expected_output

        with patch.object(code.Foo, "bar", new=mock_bar):
            f = code.Foo()
            # Just to see that our mocking worked:
            self.assertEqual(expected_output, await f.bar)
            # Should call `bar` property again:
            output = await f.func()
        self.assertEqual(expected_output, output)

References: patch docs.

Daniil Fajnberg
  • 12,753
  • 2
  • 10
  • 41
  • 1
    In my case `Foo` is something entirely different than what's under test, but your solution is what I settled on. The other option I found is patching with PropertyMock, where its side effect is a generator of un-awaited awaitables, e.g., `patch('__main__.Foo.bar', new_callable=PropertyMock, side_effect=new_awaitables())` where `new_awaitables` is essentially `while True: yield some_awaitable()`. This at least side-steps the onetime use of awaitables and does the same thing. – Brendano257 Sep 25 '22 at 16:34