11

I want to assert that one classmethod in a Python class calls another classmethod with a certain set of arguments. I would like the mocked classmethod to be "spec-ed", so it detects if it is called with the wrong number of arguments.

When I patch the classmethod using patch.object(.., autospec=True, ..), the classmethod is replaced with a NonCallableMagicMock and raises an error when I try to call it.

from mock import patch

class A(object):

    @classmethod
    def api_meth(cls):
        return cls._internal_classmethod(1, 2, 3)

    @classmethod
    def _internal_classmethod(cls, n, m, o):
        return sum(n, m, o)

with patch.object(A, '_internal_classmethod') as p:
    print(type(p).__name__)

with patch.object(A, '_internal_classmethod', autospec=True) as p:
    print(type(p).__name__)

produces the output:

MagicMock
NonCallableMagicMock

How can I get a spec-ed mock for _internal_classmethod when the class it belongs to is not mocked?

scanny
  • 26,423
  • 5
  • 54
  • 80

2 Answers2

7

There's an outstanding bug report (google code link and python bug tracker link) to fix this issue. Until the fix gets incorporated, you can try the following, which worked for me [On 2.7, though I think it would also work in 3.x].

def _patched_callable(obj):
    "Monkeypatch to allow autospec'ed classmethods and staticmethods."
    # See https://code.google.com/p/mock/issues/detail?id=241 and
    # http://bugs.python.org/issue23078 for the relevant bugs this
    # monkeypatch fixes
    if isinstance(obj, type):
        return True
    if getattr(obj, '__call__', None) is not None:
        return True
    if (isinstance(obj, (staticmethod, classmethod))
        and mock._callable(obj.__func__)):
        return True
    return False
_patched_callable._old_func = mock._callable
mock._callable = _patched_callable

After the monkeypatch, you should be able to use mock.patch normally and have static- and class-methods patched properly.

Felipe
  • 3,003
  • 2
  • 26
  • 44
6

Use spec in place of autospec, and set it directly.

with patch.object(A, '_internal_classmethod', spec=A._internal_classmethod) as p:
    print(type(p).__name__)

gives me

MagicMock

for output.

AManOfScience
  • 1,183
  • 9
  • 9
  • 7
    This solves the problem of getting back a NonCallableMagicMock, but unfortunately doesn't provide the behavior of catching call signature mismatch on the mocked classmethod. This behavior is important because it protects against mocked tests that pass even though the code itself is broken, perhaps by changing the call signature of the method. I suspect the behavior I'm looking for is a "partial autospec" and that perhaps mock doesn't yet support that sort of thing. – scanny Sep 25 '14 at 06:24