Patch method only in one module

python, python-3.x, python-mock, python-unittest, python-unittest.mock

Solution

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.

Problem

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

Original source