pythonmockingpython-unittestpython-unittest.mock

Python mock patch not mocking the object


I am using the python mocking library and I am not sure why I get this result. Why only the second one got mocked and not the first one? What's the best way to do it?

import unittest
from unittest.mock import patch, Mock

import requests
from requests import Session


def my_func():
    s = Session()
    print('my_func', type(s))


def my_func2():
    s = requests.Session()
    print('my_func2', type(s))


class Test(unittest.TestCase):

    @patch("requests.Session", new=Mock)
    def test1(self, *args):
        my_func()

    @patch("requests.Session", new=Mock)
    def test2(self, *args):
        my_func2()

Output

my_func <class 'requests.sessions.Session'>
my_func2 <class 'unittest.mock.Mock'>

Solution

  • This is a tricky nuance about Python namespaces. In the first one, my_func(), you used just Session (after importing it into your module namespace with from requests import Session). In my_func2() you used requests.Session.

    @patch('requests.Session') is replacing Session in the requests module, so requests.Session will be the mock object. But it does not replace every reference to Session in every module--in fact that would be very difficult to do in Python.

    Really, on a basic level, what patch() is doing is just setting requests.Session = Mock(). But to replace the reference to Session that's already in your module's global namespace it would have to set globals()['Session'] = Mock() as well.

    In other words, you have to patch the object in the correct namespace.

    If you have some module foo.py that contains:

    from requests import Session
    
    def my_func():
        s = Session()
        ...
    

    then a separate test module test_foo.py in which you want to mock the Session class, you have to patch it in the namespace of foo, like:

    import foo
    
    @patch('foo.Session', ...)
    def test_my_func():
        ...