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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With