如何模拟修补类的异步实例方法?

问题描述 投票:0回答:3

(以下代码可以在Jupyter中运行。) 我有一个B类,它使用A类,需要测试。

class A:
    async def f(self):
        pass

class B:
    async def f(self):
        a = A()
        x = await a.f()  # need to be patched/mocked

我有以下测试代码。看来它模拟了 A 的类方法而不是实例方法。

from asyncio import Future
from unittest.mock import MagicMock, Mock, patch

async def test():
    sut = B()
    with patch('__main__.A') as a:  # it's __main__ in Jupyter
        future = Future()
        future.set_result('result')
        a.f = MagicMock(return_value=future)
        await sut.f()

await test()

但是,代码出现错误:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
C:\Users\X~1\AppData\Local\Temp\1/ipykernel_36576/3227724090.py in <module>
     20         await sut.f()
     21 
---> 22 await test()

C:\Users\X~1\AppData\Local\Temp\1/ipykernel_36576/3227724090.py in test()
     18         future.set_result('result')
     19         a.f = MagicMock(return_value=future)
---> 20         await sut.f()
     21 
     22 await test()

C:\Users\X~1\AppData\Local\Temp\1/ipykernel_36576/3227724090.py in f(self)
      6     async def f(self):
      7         a = A()
----> 8         x = await a.f()  # need to be patched/mocked
      9 
     10 from asyncio import Future

TypeError: object MagicMock can't be used in 'await' expression
python pytest python-asyncio python-unittest
3个回答
7
投票

在 Python 3.8+ 中,修补异步方法会为您提供 AsyncMock,因此提供结果更加简单。

补丁方法本身的文档中:

如果省略 new,则如果修补的对象是异步函数,则目标将替换为 AsyncMock,否则目标将替换为 MagicMock。

AsyncMock 可以让您以更直接的方式提供返回值:

import asyncio
from unittest.mock import patch


class A:
    async def f(self):
        return "foo"


class B:
    async def f(self):
        return await A().f()


async def main():
    print(await B().f())

    with patch("__main__.A.f", return_value="bar") as p:
        print(await B().f())


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        sys.exit(1)

....打印:

$ python example.py
foo
bar

side_effect
kwarg 涵盖了您想要返回的大多数类型的值(例如,如果您需要模拟函数等待某些内容)。

  • 如果 side_effect 是一个函数,则异步函数将返回该函数的结果,
  • 如果 side_effect 是异常,则异步函数将引发异常,
  • 如果 side_effect 是一个可迭代对象,则异步函数将返回可迭代对象的下一个值,但是,如果结果序列已用完,则立即引发 StopAsyncIteration,
  • 如果未定义 side_effect,则 async 函数将返回 return_value 定义的值,因此,默认情况下,async 函数返回一个新的 AsyncMock 对象。

4
投票

需要改变

a.f = MagicMock(return_value=future)

a().f = MagicMock(return_value=future)

0
投票

我面临类似的问题,我必须返回修补的对象函数调用的值。 例如,我必须模拟

OAuthClient
有一个异步函数

我使用

new_callable=AsyncMock
来修改
@patch
装饰器的默认行为 https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch:~:text=These%20will%20be%20passed%20to%20AsyncMock%20if%20the%20patched%20object %20 是%20asynchronous%2C%20to%20MagicMock%20otherwise%20or%20to%20new_callable%20if%20specified.

class AuthService:
    def __init__(self) -> None:
        self.auth_client = AuthClient()
    
    async def get_token(payload):
        return await self.auth_client.get_token(payload)


class AuthServiceTest:
    @pytest.mark.usefixtures('auth_token_response')
    @patch("app.client.AuthClient.__init__", return_value=None, new_callable=AsyncMock)
    async def test_get_token(auth_client_async_mock, auth_token_response):
        auth_client_async_mock.get_token.return_value = auth_token_response
        service = AuthService(auth_client = auth_client_async_mock)
        response = service.get_token(
            payload={"username": "alex", "password": "strong_password", "grant_type": "password"}
        )
        # assert response
© www.soinside.com 2019 - 2024. All rights reserved.