The two locations foo.requests.get
and bar.requests.get
refer to the same object, so mock it in one place and you mock it in the other.
Imagine how you might implement patch. You have to find where the symbol is located and replace the symbol with the mock object. On exit from the with context you will need to restore the original value of the symbol. Something like (untested):
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)
So your problem is that the you are mocking the object in the request module, which you then are referring to from both foo and bar.
Following @elethan's suggestion, you could mock the requests module in foo, and even provide side effects on the get method:
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())
A more direct solution is to vary your code so that foo
and bar
pull the reference to get
directly into their name space.
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())
producing:
<MagicMock name='get().content' id='4350500992'>
b'<!DOCTYPE html>\n<html lang="en" id="facebook" ...
Updated with a more complete explanation, and with the better solution (2016-10-15)
Note: added wraps=requests.get
to call the underlying function after side effect.