Programming-fact-that-should-have-been-obvious-but-it-wasn’t Of The Day

How do you make a Python class which subclasses Mock, to extend the base Mock class with some features specific to one class from your code?

Like this, right?

    class MyClass(mock.Mock):
        def __init__(self, param1, param2):
            super(MyClass, self).__init__()

        def real_implementation_of_something(...):

This is useful when you want most methods to be mocks but there is some functionality that still needs to be there, or at least can’t be mocked automatically by the Mock class. Sadly, though, when you call any of its methods you get the following cryptic error:

    def _get_child_mock(self, **kw):
        """Create the child mocks for attributes and return value.
            By default child mocks will be the same type as the parent.
            Subclasses of Mock may want to override this to customize the way
            child mocks are made.
            For non-callable mocks the callable variant will be used (rather than
            any custom subclass)."""
        _type = type(self)
        if not issubclass(_type, CallableMixin):
            if issubclass(_type, NonCallableMagicMock):
                klass = MagicMock
            elif issubclass(_type, NonCallableMock) :
                klass = Mock
            klass = _type.__mro__[1]
>       return klass(**kw)
E       TypeError: __init__() got an unexpected keyword argument 'param1'

The first time you call a certain method on a Mock, what the object does is dynamically create another Mock object to represent the method, and save that as an attribute. My mental model of Mock was for a long time that you mocked objects, but that’s not the right way to look at it. A Mock can represent anything (if you’ve been paying attention you’ll remember that everything in Python is an object).

The problem above is that when you access, the Mock library calls the constructor MyClass.__init__() again to create a mock that represents foo. It passed in various arguments for Mock.__init__() class, but because we have subclassed Mock and overridden the constructor, this call went to MyClass.__init__() first, which choked on the unexpected parameters and gave us the weird backtrace you see above.

The fix is kind of obvious when you think about it:

    class MyClass(mock.NonCallableMock):
        def __init__(self, param1, param2):
            super(MyClass, self).__init__()

        def real_implementation_of_something(...):

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.