0

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?

berkelem
  • 2,005
  • 3
  • 18
  • 36

1 Answers1

1

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.

Erich
  • 1,838
  • 16
  • 20
  • But the `unittest_file.py` module doesn't know anything about `class_B` so I can't patch it directly. The logger for `class_B` is only referenced through the namespace of `class_A`. – berkelem Apr 21 '20 at 19:26
  • I just tried it and it works. Have you tried it out? – Erich Apr 21 '20 at 19:28
  • The arguments to unittest.mock are passed as strings- and are resolved by importing the modules in the dottetpath as the test is run. This will work, unless the `class_B.py` file cannot be found in the pythonpath when the test is run. – jsbueno Apr 21 '20 at 20:05
  • which is a pretty unrealistic scenario. did it work @berkelem? – Erich Apr 21 '20 at 20:11
  • Yes, it worked on the sample code I posted, but not on my original problem. After a little more digging I realized my sample code was not an accurate reflection of the issue I was having, because in my case I was not calling the logger directly, but through a decorator function. And it is not easy to patch a decorator because it is applied to the function on import. I'll accept your answer because it answered the question as asked. But just in case anyone has the same trouble I had, here is the answer that helped me solve my problem: https://stackoverflow.com/a/37890916/4844311 – berkelem Apr 22 '20 at 13:02