Philippe Remy
Philippe Remy

Reputation: 3123

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'>

Upvotes: 2

Views: 2394

Answers (2)

Alex_FIR_IT
Alex_FIR_IT

Reputation: 71

From documentation:

Where to patch

patch() works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.

The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined. A couple of examples will help to clarify this.

Imagine we have a project that we want to test with the following structure:

a.py
    -> Defines SomeClass

b.py
    -> from a import SomeClass
    -> some_function instantiates SomeClass

Now we want to test some_function but we want to mock out SomeClass using patch(). The problem is that when we import module b, which we will have to do then it imports SomeClass from module a. If we use patch() to mock out a.SomeClass then it will have no effect on our test; module b already has a reference to the real SomeClass and it looks like our patching had no effect.

The key is to patch out SomeClass where it is used (or where it is looked up). In this case some_function will actually look up SomeClass in module b, where we have imported it. The patching should look like:

@patch('b.SomeClass')

However, consider the alternative scenario where instead of from a import SomeClass module b does import a and some_function uses a.SomeClass. Both of these import forms are common. In this case the class we want to patch is being looked up in the module and so we have to patch a.SomeClass instead:

@patch('a.SomeClass')

Upvotes: 4

Iguananaut
Iguananaut

Reputation: 23296

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():
    ...

Upvotes: 5

Related Questions