classification
Title: unittest.mock patch autospec doesn't work on staticmethods
Type: behavior Stage: patch review
Components: Library (Lib) Versions: Python 3.8, Python 3.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: berker.peksag Nosy List: Claudiu.Popa, John Parejko2, atenni, berker.peksag, cjw296, deanliao, epu, fov, galuszkak, germanop, kevinbenton, kormat, lukasz.langa, mariocj89, michael.foord, parejkoj-3, tom.dalton.fanduel, xtreak
Priority: normal Keywords: patch

Created on 2014-12-18 08:45 by kevinbenton, last changed 2019-01-18 17:46 by xtreak.

Files
File name Uploaded Description Edit
issue23078.patch Claudiu.Popa, 2015-01-22 09:20
issue23078.patch Claudiu.Popa, 2015-01-22 17:05 review
issue23078.patch fov, 2015-09-15 00:05 Update mock._callable to properly detect classmethod/staticmethod callables review
Pull Requests
URL Status Linked Edit
PR 11613 open xtreak, 2019-01-18 17:46
PR 11613 open xtreak, 2019-01-18 17:46
Messages (16)
msg232864 - (view) Author: Kevin Benton (kevinbenton) Date: 2014-12-18 08:45
If one of the mock.patch methods is used with autospec=True on a staticmethod in an object, the mock library determines that it is not callable by checking for the __call__ attribute. This results in a NonCallableMagicMock being returned which of course dies with the following error when the mocked method is called:

TypeError: 'NonCallableMagicMock' object is not callable


It seems that the create_autospec needs to special case for classmethod and staticmethod.



The following change seems to fix it, however I am only vaguely familiar with the internals of mock so I'm not sure what this breaks.

diff -r d356250e275d mock.py
--- a/mock.py	Tue Apr 09 14:53:33 2013 +0100
+++ b/mock.py	Wed Dec 17 07:35:15 2014 -0800
@@ -2191,7 +2191,8 @@
         # descriptors don't have a spec
         # because we don't know what type they return
         _kwargs = {}
-    elif not _callable(spec):
+    elif not _callable(spec) and not isinstance(spec, (staticmethod,
+                                                       classmethod)):
         Klass = NonCallableMagicMock
     elif is_type and instance and not _instance_callable(spec):
         Klass = NonCallableMagicMock
msg234486 - (view) Author: Claudiu Popa (Claudiu.Popa) * (Python triager) Date: 2015-01-22 09:20
Here's a patch which does this. One problem could be that staticmethod(some_callable) or classmethod(some_callable) aren't callable per se, but given the fact that users expects Foo.staticmethod or Foo.klassmethod to be callable when patching them, it's something we should consider for fixing.
msg234501 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2015-01-22 17:01
Looks like b6ea3dc89a78 is not a valid changeset. Could you attach a patch from the Mercurial repo?
msg234502 - (view) Author: Claudiu Popa (Claudiu.Popa) * (Python triager) Date: 2015-01-22 17:02
Ups, sorry about that, I'll update the patch.
msg244677 - (view) Author: Felipe (fov) * Date: 2015-06-02 16:27
Regarding Claudiu's comment about `staticmethod(x)` or `classmethod(x)` not being callable, would it suffice to add a specific check of the form `(isinstance(x, (classmethod, staticmethod)) and _callable(x.__func__))`?

Separately, would it be better to include the check for `staticmethod` and `classmethod` objects (with an underlying callable) inside the `_callable` function? Not sure if this would break anything, but it seems like conceptually the issue is with the definition of a callable object, not the selection of mock type to use.
msg250716 - (view) Author: Felipe (fov) * Date: 2015-09-15 00:05
The attached patch implements these changes through _callable instead. This patch also ensures that the underlying object that staticmethod and classmethod wrap is a callable object as well.
msg305823 - (view) Author: Germano (germanop) Date: 2017-11-08 11:52
Hi,

I hit this problem wile mocking one static method and found this fix.
Tested it and works for me.
However, I did not find it pushed anywhere: no Python 3.x and no mock for 2.7.

Is there any reason why it is not pushed anywhere, yet?
msg310728 - (view) Author: Dean Liao (deanliao) Date: 2018-01-26 02:59
I planned to upgrade Chromium OS's mock module to 2.0.0. I also encountered the issue that classmethod cannot be patched as callable mock.

Reviewers, can we start the review process?
msg322849 - (view) Author: Kamil Gałuszka (galuszkak) * Date: 2018-08-01 08:09
This affects all versions from 3.4 up to 3.8-dev. Would be nice if someone could do the review of the supplied patch. 

Thanks for awesome work on Python!

I'm here because it just hit me also and I was for 1 h thinking that I don't know how to use patch/mock. ;)
msg330433 - (view) Author: Tom Dalton (tom.dalton.fanduel) Date: 2018-11-26 14:22
I've just come across this too, so would be great if the patch can be progressed.
msg330434 - (view) Author: Tom Dalton (tom.dalton.fanduel) Date: 2018-11-26 14:46
Here's a minimal example so my comment is not totally vacuous:

```
import unittest
from unittest import mock

class Foo:
    @classmethod
    def bar(cls, baz):
        pass

class TestFoo(unittest.TestCase):
    def test_bar(self):
        with unittest.mock.patch.object(Foo, "bar", autospec=True):
            Foo.bar()
```

->

```
~/› python -m unittest example.py 
E
======================================================================
ERROR: test_bar (example.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "example.py", line 14, in test_bar
    Foo.bar()
TypeError: 'NonCallableMagicMock' object is not callable

----------------------------------------------------------------------
Ran 1 test in 0.001s
```
msg330569 - (view) Author: John Parejko (John Parejko2) Date: 2018-11-28 01:58
Adding to the list of "I just ran into this". The patch submitted by fov seems straightforward enough: what can we do to help shepherd it along?
msg330570 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2018-11-28 02:05
Thanks for the pings. I will work on this issue this weekend. Note that 3.4 and 3.5 are in security-fix-only mode now, so I removed them from the versions field.
msg333734 - (view) Author: John Parejko (parejkoj-3) Date: 2019-01-15 22:10
Were you able to make any progress on this? Do you need any help?
msg333836 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python triager) Date: 2019-01-17 09:42
@berker.peksag I have converted the patch at https://bugs.python.org/file40470/issue23078.patch and pushed it to a GitHub branch https://github.com/python/cpython/compare/master...tirkarthi:bpo23078 . I am willing to open a PR attributing to @fov in case you haven't had the time to look into this. 

I am removing 3.6 since it's security fixes only mode.

Thanks
msg333892 - (view) Author: Felipe (fov) * Date: 2019-01-17 18:28
Please go ahead with the PR. I can't push this one through, but would be
great to have this finally land!

On Thu, 17 Jan 2019 at 03:42, Karthikeyan Singaravelan <
report@bugs.python.org> wrote:

>
> Karthikeyan Singaravelan <tir.karthi@gmail.com> added the comment:
>
> @berker.peksag I have converted the patch at
> https://bugs.python.org/file40470/issue23078.patch and pushed it to a
> GitHub branch
> https://github.com/python/cpython/compare/master...tirkarthi:bpo23078 . I
> am willing to open a PR attributing to @fov in case you haven't had the
> time to look into this.
>
> I am removing 3.6 since it's security fixes only mode.
>
> Thanks
>
> ----------
> nosy: +cjw296, mariocj89, xtreak
> versions:  -Python 3.6
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https://bugs.python.org/issue23078>
> _______________________________________
>
History
Date User Action Args
2019-01-18 17:47:15xtreaksetpull_requests: + pull_request11349
2019-01-18 17:46:48xtreaksetpull_requests: + pull_request11348
2019-01-17 18:28:55fovsetmessages: + msg333892
2019-01-17 09:42:31xtreaksetnosy: + cjw296, xtreak, mariocj89

messages: + msg333836
versions: - Python 3.6
2019-01-15 22:10:44parejkoj-3setnosy: + parejkoj-3
messages: + msg333734
2018-12-10 06:55:19atennisetnosy: + atenni
2018-11-28 02:05:38berker.peksagsetassignee: berker.peksag
messages: + msg330570
versions: - Python 3.4, Python 3.5
2018-11-28 01:58:02John Parejko2setnosy: + John Parejko2
messages: + msg330569
2018-11-26 14:46:50tom.dalton.fanduelsetmessages: + msg330434
2018-11-26 14:22:20tom.dalton.fanduelsetnosy: + tom.dalton.fanduel
messages: + msg330433
2018-08-01 08:09:51galuszkaksetnosy: + galuszkak

messages: + msg322849
versions: + Python 3.6, Python 3.7, Python 3.8
2018-01-26 02:59:15deanliaosetnosy: + deanliao
messages: + msg310728
2017-11-08 11:52:18germanopsetnosy: + germanop
messages: + msg305823
2016-02-10 17:27:22epusetnosy: + epu
2015-09-15 00:05:30fovsetfiles: + issue23078.patch

messages: + msg250716
2015-08-18 14:33:44kormatsetnosy: + kormat
2015-06-02 16:27:05fovsetnosy: + fov
messages: + msg244677
2015-04-14 15:16:05kjachimsetnosy: + lukasz.langa
2015-01-22 17:05:21Claudiu.Popasetfiles: + issue23078.patch
2015-01-22 17:02:27Claudiu.Popasetmessages: + msg234502
2015-01-22 17:01:33berker.peksagsetnosy: + berker.peksag

messages: + msg234501
versions: - Python 2.7
2015-01-22 09:20:16Claudiu.Popasetfiles: + issue23078.patch

components: + Library (Lib), - Tests

keywords: + patch
nosy: + Claudiu.Popa
messages: + msg234486
stage: patch review
2014-12-18 08:45:23kevinbentoncreate