classification
Title: unittest.mock.patch() cannot properly mock methods
Type: Stage:
Components: Library (Lib) Versions: Python 3.10, Python 3.9, Python 3.8, Python 3.7, Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: CendioOssman, cjw296, mariocj89, michael.foord, xtreak
Priority: normal Keywords:

Created on 2020-12-03 14:41 by CendioOssman, last changed 2020-12-16 17:12 by mariocj89.

Messages (2)
msg382415 - (view) Author: Pierre Ossman (CendioOssman) Date: 2020-12-03 14:41
unittest.mock.patch() as it currently works cannot properly mock a method as it currently replaces it with something more mimicking a function. I.e. the descriptor magic that includes "self" isn't properly set up.

In most cases this doesn't really matter, but there are a few use cases where this is important:

1. Calling base classes where you need to make sure it works regardless of super() or direct reference to the base class.

2. Multiple objects calling the same base class using super(). Without the self argument you can't tell the calls apart.

3. Setting up a side_effect that needs access to the object. In some cases you can pass the object using some side channel, but not all. E.g. not when mocking a base class' __init__(). (already reported as Issue35577).

Right now you can work around this by using autospec, as that has the undocumented side-effect of properly setting up methods. So don't fix Issue41915 before this one or we lose that workaround. :)
msg383189 - (view) Author: Mario Corchero (mariocj89) * (Python triager) Date: 2020-12-16 17:12
Right, I believe this is indeed broken.

This code:
```
class A:
    def m(self, a=None): pass

from unittest import mock
with mock.patch.object(A, "m", autospec=True):
    A.m.side_effect = print
    A().m(1)
```

prints: <__main__.A object at 0x7f69cc7dc6a0> 1

Whilst the same code without autospec:
```
class A:
    def m(self, a=None): pass

from unittest import mock
with mock.patch.object(A, "m"):
    A.m.side_effect = print
    A().m(1)
```

prints: 1

This is a rather tricky issue though, as changing the behaviour to pass self would break too many people (even if it would probably the be "right" fix).

You can indeed use autospec or just write a descriptor that does the proper bound method logic:

```
class A:
    def m(self, a=None): pass

def descriptor(self, obj, objtype=None):
    self._self = obj
    def _(*args):
        return self(obj, *args)
    return _

from unittest import mock
with mock.patch.object(A, "m"):
    A.m.side_effect = print
    A.m.__get__ = descriptor
    A().m(1)
```

When patching a method at the class level, today Mock is basically hiding "self" when used without a spec. We should decide whether:
- that is OK and we want to "fix" autospec to do the same (remove self)
- Leave things as they are, even if not perfect
- Pass self, breaking most assertions across the world (sounds like a bad idea initially)
- Create some opt-in parameter or a custom thing like PropertyMock to pass self (MethodMock?).

TBH, I don't have a good answer :/. I'm subscribing other more insightful people to the issue, maybe this is the way it is intended to work :).
History
Date User Action Args
2020-12-16 17:12:39mariocj89setnosy: + cjw296, michael.foord, mariocj89, xtreak
messages: + msg383189
2020-12-03 14:41:06CendioOssmancreate