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
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.