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
        else:
            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 MyClass.foo(), 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(...):
            ...
Advertisements

About Sam Thursfield

Who's that kid in the back of the room? He's setting all his papers on fire! Where did he get that crazy smile? We all think he's really weird.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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 )

Google+ photo

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

Connecting to %s