这两个位置引用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 中模拟 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
一个更直接的解决方案是改变你的代码,以便bar
将引用get
直接拉到它们的名称空间中。
foo.py:
from requests import get
def get_ip():
return get('http://jsonip.com/').content
酒吧.py:
from requests import get
def get_ip():
return get('https://fb.com/').content
主要.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
后调用底层函数有副作用。