There are a number of similar(ish) questions here about how, in Python, you are supposed to patch the superclasses of your class, for testing. I've gleaned some ideas from them, but I'm still not where I need to be.
Imagine I have two base classes:
class Foo(object):
def something(self, a):
return a + 1
class Bar(object):
def mixin(self):
print("Hello!")
Now I define the class that I want to test as such:
class Quux(Foo, Bar):
def something(self, a):
self.mixin()
return super().something(a) + 2
Say I want to test that mixin
has been called and I want to replace the return value of the mocked Foo.something
, but importantly (and necessarily) I don't want to change any of the control flow or logic in Quux.something
. Presuming patching superclasses "just worked", I tried unittest.mock.patch
:
with patch("__main__.Foo", spec=True) as mock_foo:
with patch("__main__.Bar", spec=True) as mock_bar:
mock_foo.something.return_value = 123
q = Quux()
assert q.something(0) == 125
mock_bar.mixin.assert_called_once()
This doesn't work: The superclasses' definitions of something
and mixin
aren't being mocked when Quux
is instantiated, which is not unsurprising as the class' inheritance is defined before the patch.
I can get around the mixin
problem, at least, by explicitly setting it:
# This works to mock the mixin method
q = Quux()
setattr(q, "mixin", mock_bar.mixin)
However, a similar approach doesn't work for the overridden method, something
.
As I mentioned, other answers to this question suggest overriding Quux
's __bases__
value with the mocks. However, this doesn't work at all as __bases__
must be a tuple of classes and the mocks' classes appear to just be the originals:
# This doesn't do what I want
Quux.__bases__ = (mock_foo.__class__, mock_bar.__class__)
q = Quux()
Other answers suggested overriding super
. This does work, but I feel that it's a bit dangerous as any calls to super
you don't want to patch will probably break things horribly.
So is there a better way of doing what I want than this:
with patch("builtins.super") as mock_super:
mock_foo = MagicMock(spec=Foo)
mock_foo.something.return_value = 123
mock_super.return_value = mock_foo
mock_bar = MagicMock(spec=Bar)
q = Quux()
setattr(q, "mixin", mock_bar.mixin)
assert q.something(0) == 125
mock_bar.mixin.assert_called_once()
The matter is actually simple -
the subclass will contain a reference to the original classes
inside its own structure (the public visible attributes __bases__
and __mro__
). That reference is not changed when you mock those base classes -
the mocking would only affect one using those objects explicitly, while the patching is "turned on". In other words, they would only be used if your Quux
class would itself be defined inside the with
blocks. And that would not work either, as the "mock" object replacing the classes can not be a proper superclass.
However, the workaround, and the right way to do it are quite simple - you just have to mock the methods you want replaced, not the classes.
The question is a bit old now, and I hope you had moved on, but the right thing to do there is:
with patch("__main__.Foo.something", spec=True) as mock_foo:
with patch("__main__.Bar.mixin", spec=True) as mock_bar:
mock_foo.return_value = 123
q = Quux()
assert q.something(0) == 125
mock_bar.assert_called_once()
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