2ank3th
2ank3th

Reputation: 3109

How does MagicMock avoid throwing AttributeError when a random method is called?

In Python, if you call a method that doesn't exist it throws an AttributeError. Ex

>>> class A:
...     def yo(self):
...             print(1)
... 
>>> a = A()
>>> a.yo()
1
>>> a.hello()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'hello'

In the below code, MagicMock class doesn't have a function named hello or no patch was created for a method hello. Still below code doesn't throw an AttributeError

>>> from unittest.mock import MagicMock 
>>> obj = MagicMock()
>>> obj.hello()
<MagicMock name='mock.hello()' id='4408758568'>

How is MagicMock able to do this? How can I create a class that can perform an action when any method (which might not be defined) is called on it?

Upvotes: 3

Views: 2122

Answers (2)

wim
wim

Reputation: 363586

The Python datamodel documents a hook, __getattr__, which shall be called when attribute access fails to resolve in the usual ways. Mocks use it to return a new mock instance - i.e. mocks define unknown attributes as factories.

Reproducing mock's implementation in a simpler way, you would just turn __getattr__ and __call__ into factory functions:

class M:
    def __call__(self):
        return M()
    def __getattr__(self, name):
        return M()

Example usage:

>>> mock = M()
>>> mock.potato
<__main__.M at 0xdeadbeef>
>>> mock.potato()
<__main__.M at 0xcafef00d>

How is MagicMock able to do this?

This part is not specific to MagicMock, an ordinary Mock will do the same (the "magic" in the name is just referring to additional features allowing better mocking of magic methods). MagicMock inherits such behavior from one of the base classes:

>>> MagicMock.mro()
[unittest.mock.MagicMock,
 unittest.mock.MagicMixin,
 unittest.mock.Mock,
 unittest.mock.CallableMixin,
 unittest.mock.NonCallableMock,  # <--- this one overrides __getattr__!
 unittest.mock.Base,
 object]

How can I create a class that can perform an action when any method (which might not be defined) is called on it?

It depends if you want to be in front of, or behind, a normal attribute access. If you want to get in front, you should define __getattribute__, it's called unconditionally to implement attribute accesses before searching the class/instance namespaces. However, if you want to take a lower precedence to normal attributes (i.e. those living in the object __dict__) and to descriptors, then you should define __getattr__ as previously discussed.

Upvotes: 2

I don't actually know how specifically MagicMock works (I've never used it, but I've Heard Good Things), but this part of the behaviour can be replicated (along with probably multiple other possible solutions) by hijacking __getattr__ in a way that it returns a callable that creates a new mock instance when called:

class MM:
    def __init__(self, name=None):
        # store a name, TODO: random id, etc.
        self.name = name

    def __repr__(self):
        # make it pretty
        if self.name:
            r = f'<MM name={self.name}>'
        else:
            r = f'<MM>'
        return r

    def __getattr__(self, attrname):
        # we want a factory for a mock instance with a name corresponding to attrname
        def magicattr():
            return MM(name=f"'mock.{attrname}()'")
        return magicattr

When executed, we see the following:

>>> MM()
<MM>
>>> MM().hello()
<MM name='mock.hello()'>

I didn't go overboard with defining an id and whatnot, but the basic trick can be seen on the above stripped-down example.

The way the above works is that accessing .hello or any other attribute goes through our custom __getattr__ which gives us a chance to generate a fake (mocked) method on the fly, with whatever properties we want to. As I understand one of the many benefits of MagicMock is exactly that we don't have to worry about AttributeErrors being thrown by default, it just works.

Upvotes: 1

Related Questions