I'm trying to dynamically add classmethods to a Python 2.7 class. I also tested the same issue in 3.5.1 and the same thing happens. The purpose of this is for unit test mocking. The answers here How to add a classmethod in Python dynamically seem relevant, but they seem to break when inheritance also plays a role. When my mock is used, the cls binding always get the parent class, not the class I called the classmethod through. Why is this happening and how do I achieve mocking a classmethod and getting the right class in cls as well?
class Parent(object):
@classmethod
def clm(cls):
return 'Orig:' + cls.__name__
class Child(Parent):
pass
print 'Parent:', Parent.clm()
# Parent: Orig:Parent as expected
print 'Child:', Child.clm()
# Child: Orig:Child as expected as well, because cls is Child
def mock_clm(cls):
return 'Ovr:' + cls.original_clm()
# Save the original and replace with mock function
Parent.original_clm = Parent.clm
Parent.clm = classmethod(mock_clm)
print 'Parent:', Parent.clm()
# Parent: Ovr:Orig:Parent as expected, our mock function is called
print 'Child:', Child.clm()
# Child: Ovr:Orig:Parent! What I wanted here is to get Ovr:Orig:Child
# but for some reason the classmethod cls binding breaks and returns
# Parent instead
# Note how original_clm doesn't bind as I expected as well
print 'Parent:', Parent.original_clm()
print 'Child:', Child.original_clm()
I've tried various other options for mocking like using mock.patch, using setattr on the parent class and adding the decorator to the function instead of using it on assigment, all to the same effect. Here's example code using mock that produces the same exact result:
import mock
with mock.patch('__main__.Parent.clm', classmethod(mock_clm)):
print 'Parent:', Parent.clm()
# Parent: Ovr:Orig:Parent
print 'Child:', Child.clm()
# Child: Ovr:Orig:Parent