0

I want to patch a class in a Python library and mock its methods independently in several tests. My approach works if I run the tests separately. However, if I run the tests using $ python -m unittest test_*.py the second test appears to use the patched object from the first and fails.

Here's a simplified version of my setup. (In the real scenario I have a more complex dependency on the mocked class and multiple tests in each test class using different mock functions as side effect.)

Files:

lib.py
test_1.py
test_2.py

lib.py:

from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
    
client = BackendApplicationClient(client_id='a client')
session = OAuth2Session(client=client)
    
def get_data():
  return session.get('https://some.url.com/data')

test_1.py:

import unittest
from unittest.mock import patch

with patch('requests_oauthlib.OAuth2Session') as MockSession:
  mock_session = MockSession.return_value
  from .lib import get_data
    
def mock_get(url):
  return f'some data from {url}'
    
class Test(unittest.TestCase):
  def test_get_data(self):
    mock_session.get.side_effect = mock_get
    self.assertEqual(get_data(), 'some data from https://some.url.com/data')

test_2.py:

import unittest
from unittest.mock import patch
    
with patch('requests_oauthlib.OAuth2Session') as MockSession:
  mock_session = MockSession.return_value
  from .lib import get_data
    
def mock_get(url):
  return f'other data from {url}'
    
class Test(unittest.TestCase):
  def test_get_data(self):
    mock_session.get.side_effect = mock_get
    self.assertEqual(get_data(), 'other data from https://some.url.com/data')

When doing

$ python -m unittest test_1.py
$ python -m unittest test_2.py

both tests yield OK.

Doing

$ python -m unittest test_*.py

fails with

AssertionError: 'some data from https://some.url.com/data' != 'other data from https://some.url.com/data'

I also tried to use patch() like so:

patcher = patch('requests_oauthlib.OAuth2Session')
MockSession = patcher.start()
mock_session = MockSession()
from .lib import get_data

and introduce explicit module teardown:

def tearDownModule():
  patcher.stop()

but got the same results.

Finally, I tried moving the patch() into a class setup:

import unittest
from unittest.mock import patch

my_get_data = None

def mock_get(url):
  return f'some data from {url}'

class Test(unittest.TestCase):
  patcher = None
  mock_session = None

  @classmethod
  def setUpClass(cls):
    global my_get_data
    cls.patcher = patch('requests_oauthlib.OAuth2Session')
    MockSession = cls.patcher.start()
    cls.mock_session = MockSession()
    from python3.test.operators.com.sap.mdi.consumer.v2.tmp_c.lib import get_data
    my_get_data = get_data
    
  @classmethod
  def tearDownClass(cls):
    cls.patcher.stop()

  def test_get_data(self):
    self.mock_session.get.side_effect = mock_get
    self.assertEqual(my_get_data(), 'some data from https://some.url.com/data')

but still got the same AssertionError.

What am I doing wrong?

Kai Roesner
  • 429
  • 3
  • 17
  • You're using `patch` as a context manager, but you're using the patched object in your tests after the context has exited. I'm surprised that this works at all! Try moving your `with patch...` into the actual test function. – Samwise Feb 21 '22 at 16:40
  • @Samwise, I guess it works because the patch is only relevant for the `import` inside the context. Since in my real use case the test class has many test functions, moving the `with patch()` inside the test functions would bloat the test code which I would like to avoid. Besides it doesn't solve the problem: Just tried your suggestion and it still gives the same `FAILED` result. Also when using the `patcher` object this should not be an issue. – Kai Roesner Feb 21 '22 at 16:52
  • Move your setup code into a setup function (class `setUp` or `moduleSetUp`), so it won't get executed at load time, otherwise both patches are done before any test is run. – MrBean Bremen Feb 21 '22 at 16:59
  • @MrBeanBremen, thanks for the hint, hadn't thought of that... alas, doesn't help either... (same with module setup/teardown) – Kai Roesner Feb 21 '22 at 17:29
  • 1
    The other problem that I hadn't noticed is that `get_data` is imported only once - if you import a module that is already in the module cache it won't get imported again. To reload it, you need to `importlib.reload` it (and maybe remove from the module cache before). – MrBean Bremen Feb 21 '22 at 17:32
  • @MrBeanBremen, that sounds very much like it is the crux of the matter. Apart from my own lib the patched `requests_oauthlib.OAuth2Session` is likely also affected. So I'm definitely going to try that. However, having read [this question](https://stackoverflow.com/questions/46877678) and the references therein, I'm a bit doubtful whether reloading the module and/or removing it from the cache will solve my problem. In the end I might need to have all the tests in one module... – Kai Roesner Feb 22 '22 at 07:40
  • @MrBeanBremen, removing both `requests_oauthlib` and my own module from the cache before the patched imports did the trick. It then works even with my original version of the tests using `patch()` as a context manager outside the test class. Thanks a lot! If you put that down as answer I'll accept it. – Kai Roesner Feb 22 '22 at 09:08

0 Answers0