For example, I have some module(foo.py
) with next code:
import requests
def get_ip():
return requests.get('http://jsonip.com/').content
And module bar.py
with similiar code:
import requests
def get_fb():
return requests.get('https://fb.com/').content
I just can't understand why next happens:
from mock import patch
from foo import get_ip
from bar import get_fb
with patch('foo.requests.get'):
print(get_ip())
print(get_fb())
They are two mocked:
<MagicMock name='get().content' id='4352254472'>
<MagicMock name='get().content' id='4352254472'>
It is seemed to patch only foo.get_ip
method due to with patch('foo.requests.get')
, but it is not.
I know that I can just get bar.get_fb
calling out of with
scope, but there are cases where I just run in context manager one method that calls many other, and I want to patch requests
only in one module.
Is there any way to solve this? Without changing imports in module
答案 0 :(得分:2)
两个位置foo.requests.get
和bar.requests.get
引用同一个对象,因此在一个地方嘲笑它,然后在另一个地方嘲笑它。
想象一下如何实现补丁。您必须找到符号所在的位置,并用模拟对象替换该符号。退出with上下文时,您需要恢复符号的原始值。像(未经测试)的东西:
class patch(object):
def __init__(self, symbol):
# separate path to container from name being mocked
parts = symbol.split('.')
self.path = '.'.join(parts[:-1]
self.name = parts[-1]
def __enter__(self):
self.container = ... lookup object referred to by self.path ...
self.save = getattr(self.container, name)
setattr(self.container, name, MagicMock())
def __exit__(self):
setattr(self.container, name, self.save)
所以你的问题是你在模拟请求模块中的对象,然后你从foo和bar中引用它。
按照@ elethan的建议,您可以在foo中模拟请求模块,甚至可以为get方法提供副作用:
from unittest import mock
import requests
from foo import get_ip
from bar import get_fb
def fake_get(*args, **kw):
print("calling get with", args, kw)
return mock.DEFAULT
replacement = mock.MagicMock(requests)
replacement.get = mock.Mock(requests.get, side_effect=fake_get, wraps=requests.get)
with mock.patch('foo.requests', new=replacement):
print(get_ip())
print(get_fb())
更直接的解决方案是改变您的代码,以便foo
和bar
将对get
的引用直接拉入其名称空间。
foo.py:
from requests import get
def get_ip():
return get('http://jsonip.com/').content
bar.py:
from requests import get
def get_ip():
return get('https://fb.com/').content
main.py:
from mock import patch
from foo import get_ip
from bar import get_fb
with patch('foo.get'):
print(get_ip())
print(get_fb())
制造
<MagicMock name='get().content' id='4350500992'>
b'<!DOCTYPE html>\n<html lang="en" id="facebook" ...
更新了更完整的解释,并提供了更好的解决方案(2016-10-15)
注意:添加wraps=requests.get
以在副作用后调用基础函数。
答案 1 :(得分:1)
不要窃取 @Neapolitan 的雷声,但另一种选择就是简单地模仿foo.requests
而不是foo.requests.get
:
with patch('foo.requests'):
print(get_ip())
print(get_fb())
我认为两种方法在您的案例中被嘲笑的原因是,由于requests.get
未明确导入foo.py
,mock
必须在requests
中查找方法{1}}模块并在那里进行模拟,而不是在已导入requests
的{{1}}对象中进行模拟,以便foo
稍后导入bar
并访问{{ 1}}它正在制作模拟版本。但是,如果您requests
requests.get
,则只需修补已导入patch
的模块对象,原始foo.requests
模块不会受到影响。
虽然对此特定问题没有特别帮助,this article对于理解foo