如何模拟已打补丁 class 的异步实例方法?
How to mock an async instance method of a patched class?
(以下代码在Jupyter中可以运行。)
我有一个class B,它使用class 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 的 class 方法而不是实例方法。
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/ipykernel_36576/3227724090.py in <module>
20 await sut.f()
21
---> 22 await test()
C:\Users\X~1\AppData\Local\Temp/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/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
需要改变
a.f = MagicMock(return_value=future)
至
a().f = MagicMock(return_value=future)
在 Python 3.8+ 中,修补异步方法会为您提供 AsyncMock,因此提供结果会更直接一些。
在 the patch method itself 的文档中:
If new is omitted, then the target is replaced with an AsyncMock if the patched object is an async function or a MagicMock otherwise.
AsyncMock 允许您以更直接的方式提供 return 值:
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 涵盖了您要查找的大多数类型的值 return(例如,如果您需要模拟函数 await something)。
- if side_effect is a function, the async function will return the result of that function,
- if side_effect is an exception, the async function will raise the exception,
- if side_effect is an iterable, the async function will return the next value of the iterable, however, if the sequence of result is exhausted, StopAsyncIteration is raised immediately,
- if side_effect is not defined, the async function will return the value defined by return_value, hence, by default, the async function returns a new AsyncMock object.
(以下代码在Jupyter中可以运行。) 我有一个class B,它使用class 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 的 class 方法而不是实例方法。
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/ipykernel_36576/3227724090.py in <module>
20 await sut.f()
21
---> 22 await test()
C:\Users\X~1\AppData\Local\Temp/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/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
需要改变
a.f = MagicMock(return_value=future)
至
a().f = MagicMock(return_value=future)
在 Python 3.8+ 中,修补异步方法会为您提供 AsyncMock,因此提供结果会更直接一些。
在 the patch method itself 的文档中:
If new is omitted, then the target is replaced with an AsyncMock if the patched object is an async function or a MagicMock otherwise.
AsyncMock 允许您以更直接的方式提供 return 值:
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 涵盖了您要查找的大多数类型的值 return(例如,如果您需要模拟函数 await something)。
- if side_effect is a function, the async function will return the result of that function,
- if side_effect is an exception, the async function will raise the exception,
- if side_effect is an iterable, the async function will return the next value of the iterable, however, if the sequence of result is exhausted, StopAsyncIteration is raised immediately,
- if side_effect is not defined, the async function will return the value defined by return_value, hence, by default, the async function returns a new AsyncMock object.