So I just spent a couple of hours banging my head on a ridiculous PyMox mocking issue and though I'd share. Here is an example of the existing code.
# views.py
class MyBaseView(BaseView):
def get_stuff(self):
# this is doing things
class SpecificView(MyBaseView):
example = True
def send_stuff(self):
stuff = self.get_stuff()
# do other things
# tests.py
class SpecificViewTest(TestCase):
def setUp(self):
self.view = SpecificView()
self.mox = mox.Mox()
def tearDown(self):
self.mox.UnsetStubs()
def test_send_stuff(self):
self.mox.StubOutWithMock(MyBaseView, 'get_stuff')
MyBaseView.get_stuff().AndReturn(['list', 'of', 'things'])
self.mox.ReplayAll()
self.view.send_stuff()
self.mox.VerifyAll()
# do assertions
This was all fine and working for months, until I added the following.
# views.py
class ExtraSpecificView(SpecificView):
def other_stuff(self):
self.get_stuff()
# do more things again
# test.py
class ExtraSpecificViewTest(TestCase):
def setUp(self):
self.view = ExtraSpecificView()
self.mox = mox.Mox()
def tearDown(self):
self.mox.UnsetStubs()
def test_other_stuff(self):
self.mox.StubOutWithMox(SpecifcView, 'get_stuff')
SpecificView.get_stuff().AndReturn(['more', 'things'])
self.mox.ReplayAll()
self.view.other_stuff()
self.mox.VerifyAll()
So what started happening at this point is SpecificViewTest.test_send_stuff
began failing with this.
ExpectedMethodCallsError: Verify: Expected methods never called:
0. get_stuff.__call__() -> ['list', 'of', 'things']
It would only fail when ExtraSpecificViewTest
ran before SpecificViewTest
which, with nose, it does by default. So I knew it had to have something to do with the mocking in ExtraSpecificViewTest.test_other_stuff
. After more playing and banging my head I came up with this working theory which seems correct the more I think on it.
When you mock out a method on a class, the mocking library stores the original method and replaces it with the mock function. When you tell the library to stop mocking the method, mox.UnsetStubs
in this case, it grabs that original method it stored before and assigns it back to the appropriate attribute on the class, restoring it's original functionality.
However, in my class I was mocking a method that SpecificView
inherited from MyBaseView
. When the library grabs the original method for storing Python sees that SpecificView doesn't have a get_stuff
method of it's own, so it moves up the MRO and grabs MyBaseView.get_stuff
and the library stores that and assigns a mock function to the get_stuff
attribute on the class of SpecificView
. Then, when the library goes to un-stub the method, it grabs the stored get_stuff
Python gave it from MyBaseView
and assigns it also to the get_stuff
attribute of the class of SpecificView
. Finally, when SpecificViewTest.test_set_stuff
runs, send_stuff
calls SpecificView
's get_stuff
, instead of moving up the MRO as to used to, Python now sees that the SpecificView
class has it's own get_stuff
method (which doesn't have a super call), so it doesn't move up the MRO, therefore MyBaseView
is never called as expected and raising a failure when verified.
So this became the fix.
#tests.py
class ExtraSpecificViewTest(TestCase):
...
def test_other_stuff(self):
self.mox.StubOutWithMox(MyBaseView, 'get_stuff')
MyBaseView.get_stuff().AndReturn(['more', 'things'])
It's a convoluted bug that may or may not be specific to PyMox or other mocking libraries, but I'm not sure how they should be properly handling something like this. So I guess the moral of the story is to mock methods from the class which the were originally defined, not just the class you inherited from.