Reputation: 3123
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
Reputation: 71
From documentation:
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
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