Search code examples
pythonunit-testingimportmonkeypatching

Mocking an object in a distant module


I have some unittests that I am running on a class, ClassA. This class has methods that call ClassB. ClassA and ClassB are in separate modules, class_A.py and class_B.py. Both of these modules have logger functions that are available globally within that module.

During my test of the methods of ClassA I want to suppress the logging messages. Using mock.patch it is straightforward to do this for the logger in class_A.py. But I have not found a way to patch the logger in class_B.py.

Here is some sample code:

unittest_file.py

import unittest
import class_A

class TestClassA(unittest.TestCase):

    def setUp(self):
        pass

    def test_method(self):
        my_class_A = class_A.ClassA()
        my_class_A.run_method()

    def tearDown(self):
        pass

class_A.py

import logging
from class_B import ClassB

logger = logging.getLogger(__name__)

class ClassA:

    run_method(self):
        my_class_B = ClassB()
        my_class_B.do_something()
        logger.info("Running method on Class A")

class_B.py

import logging

logger = logging.getLogger(__name__)

class ClassB:

    do_something(self):
        logger.info("Doing something on Class B")

To patch the logger in class_A.py I can add @patch('__main__.unittest_file.ClassA.logger', autospec=True) as a decorator to the test_method() method. But I don't have direct access to the logger in class_B.py to patch it as well.

The closest I have come to a solution is this:

@patch(eval('__main__.unittest_file.class_A.ClassB.__module__')['logger'])

but the eval() function applied to the module name doesn't preserve the import location, so it doesn't patch the logger in the right namespace.

I know I could easily solve this by changing the import statement in class_A.py to import class_B instead of from class_B import ClassB, but I don't want to edit the code in class_A.py or class_B.py, so that's not the answer I'm looking for.

I also know there are ways around this by disabling logging, as in this question but I am more interested in being able to fine-tune the control I have over different parts of the code to be tested, so that's not the answer I'm looking for either.

My question is: Is there a way that I can navigate from an imported object to other objects in its parent module, while staying in the same namespace?


Solution

  • For me simply patching class_A.logger and class_B.logger works, e.g.

    import unittest
    from unittest import mock
    
    class TestCaseA(unittest.TestCase):
    
        @mock.patch('class_A.logger')
        @mock.patch('class_B.logger')
        def test_method(self, mockedA, mockedB):
            my_class_A = ClassA()
            my_class_A.run_method()
    

    Both loggers don't output log messsages anymore.