(Nota bene: This is heavily modified from the original question, to include details I erroneously elided.)
This is the (summarized) file (common.py) I'm testing. It contains a decorator (derived from the Decorum library) that calls a class method on another object(A): I want to patch out A, because that code makes an external call I'm not testing.
from decorum import Decorum
class A:
@classmethod
def c(cls):
pass
class ClassyDecorum(Decorum):
"""Hack to allow decorated instance methods of a class object to run with decorators.
Replace this once Decorum 1.0.4+ comes out.
"""
def __get__(self, instance, owner):
from functools import partial
return partial(self.call, instance)
class B(Decorum):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def init(self, *args, **kwargs):
A.c()
return super().init(*args, **kwargs)
I'd like to @patch class A in my unittest, to isolate and check B.d()'s functionality. This is my unittest (located in test/test_common.py):
class BDecoratedClass(MagicMock):
@B
def dummy_func(self):
return "Success"
class TestB(TestCase):
@patch('unittest_experiment.A', autospec=True)
def test_d(self, mock_a):
b = BDecoratedClass()
b.dummy_func()
mock_a.c.assert_called_once_with() # Fails
Debugging the above, I see that A is never actually mocked: the code proceeds into A's code, so it makes sense that mock_a is never called, and thus the assertion fails. However, I'd like to properly monkey patch A. This approach works if I'm monkey patching an import that exists in common.py, but apparently not if the class is defined there?
Note that I think this is likely an issue of where I'm patching, that is @patch('common.A', autospec=True) should more likely be something like @patch('where.python.actually.finds.A.when.B.calls.A', autospec=True). But I'm very unclear on how to determine if that is the case, and if so, what the correct path is. For instance, @patch('BDecorated.common.A', autospec=True) does not work.
It looks like patch substitutes the import and that's why it's too late at that point unless you work around it like you explained in your the answer. I found that using patch.object for individual methods also work. So something like:
class TestB(TestCase):
@patch.object(A, 'c')
def test_d(self, mock_c):
b = BDecoratedClass()
b.dummy_func()
mock_c.assert_called_once_with() # Succeeds
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With