两个地点foo.requests.get
and 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 中的 requests 模块,甚至对 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
and 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
在副作用发生后调用底层函数。