Skip to content

Commit 379b623

Browse files
use getattr_static in spy instead of __getattributes__ (#224)
Co-authored-by: Bruno Oliveira <[email protected]>
1 parent 2de3e9a commit 379b623

File tree

3 files changed

+74
-48
lines changed

3 files changed

+74
-48
lines changed

src/pytest_mock/_util.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from typing import Union
2+
3+
_mock_module = None
4+
5+
6+
def get_mock_module(config):
7+
"""
8+
Import and return the actual "mock" module. By default this is
9+
"unittest.mock", but the user can force to always use "mock" using
10+
the mock_use_standalone_module ini option.
11+
"""
12+
global _mock_module
13+
if _mock_module is None:
14+
use_standalone_module = parse_ini_boolean(
15+
config.getini("mock_use_standalone_module")
16+
)
17+
if use_standalone_module:
18+
import mock
19+
20+
_mock_module = mock
21+
else:
22+
import unittest.mock
23+
24+
_mock_module = unittest.mock
25+
26+
return _mock_module
27+
28+
29+
def parse_ini_boolean(value: Union[bool, str]) -> bool:
30+
if isinstance(value, bool):
31+
return value
32+
if value.lower() == "true":
33+
return True
34+
if value.lower() == "false":
35+
return False
36+
raise ValueError("unknown string for bool: %r" % value)

src/pytest_mock/plugin.py

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,9 @@
1818

1919
import pytest
2020

21-
_T = TypeVar("_T")
22-
23-
24-
def _get_mock_module(config):
25-
"""
26-
Import and return the actual "mock" module. By default this is
27-
"unittest.mock", but the user can force to always use "mock" using
28-
the mock_use_standalone_module ini option.
29-
"""
30-
if not hasattr(_get_mock_module, "_module"):
31-
use_standalone_module = parse_ini_boolean(
32-
config.getini("mock_use_standalone_module")
33-
)
34-
if use_standalone_module:
35-
import mock
36-
37-
_get_mock_module._module = mock
38-
else:
39-
_get_mock_module._module = unittest.mock
21+
from ._util import get_mock_module, parse_ini_boolean
4022

41-
return _get_mock_module._module
23+
_T = TypeVar("_T")
4224

4325

4426
class PytestMockWarning(UserWarning):
@@ -54,7 +36,7 @@ class MockerFixture:
5436
def __init__(self, config: Any) -> None:
5537
self._patches = [] # type: List[Any]
5638
self._mocks = [] # type: List[Any]
57-
self.mock_module = mock_module = _get_mock_module(config)
39+
self.mock_module = mock_module = get_mock_module(config)
5840
self.patch = self._Patcher(
5941
self._patches, self._mocks, mock_module
6042
) # type: MockerFixture._Patcher
@@ -99,20 +81,14 @@ def spy(self, obj: object, name: str) -> unittest.mock.MagicMock:
9981
:return: Spy object.
10082
"""
10183
method = getattr(obj, name)
102-
103-
autospec = inspect.ismethod(method) or inspect.isfunction(method)
104-
# Can't use autospec classmethod or staticmethod objects
105-
# see: https://bugs.python.org/issue23078
106-
if inspect.isclass(obj):
107-
# Bypass class descriptor:
108-
# http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static
109-
try:
110-
value = obj.__getattribute__(obj, name) # type:ignore
111-
except AttributeError:
112-
pass
113-
else:
114-
if isinstance(value, (classmethod, staticmethod)):
115-
autospec = False
84+
if inspect.isclass(obj) and isinstance(
85+
inspect.getattr_static(obj, name), (classmethod, staticmethod)
86+
):
87+
# Can't use autospec classmethod or staticmethod objects before 3.7
88+
# see: https://bugs.python.org/issue23078
89+
autospec = False
90+
else:
91+
autospec = inspect.ismethod(method) or inspect.isfunction(method)
11692

11793
def wrapper(*args, **kwargs):
11894
spy_obj.spy_return = None
@@ -518,7 +494,7 @@ def wrap_assert_methods(config: Any) -> None:
518494
if _mock_module_originals:
519495
return
520496

521-
mock_module = _get_mock_module(config)
497+
mock_module = get_mock_module(config)
522498

523499
wrappers = {
524500
"assert_called": wrap_assert_called,
@@ -594,16 +570,6 @@ def pytest_addoption(parser: Any) -> None:
594570
)
595571

596572

597-
def parse_ini_boolean(value: Union[bool, str]) -> bool:
598-
if isinstance(value, bool):
599-
return value
600-
if value.lower() == "true":
601-
return True
602-
if value.lower() == "false":
603-
return False
604-
raise ValueError("unknown string for bool: %r" % value)
605-
606-
607573
def pytest_configure(config: Any) -> None:
608574
tb = config.getoption("--tb", default="auto")
609575
if (

tests/test_pytest_mock.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ def test_mock_patch_dict_resetall(mocker: MockerFixture) -> None:
162162
],
163163
)
164164
def test_mocker_aliases(name: str, pytestconfig: Any) -> None:
165-
from pytest_mock.plugin import _get_mock_module
165+
from pytest_mock._util import get_mock_module
166166

167-
mock_module = _get_mock_module(pytestconfig)
167+
mock_module = get_mock_module(pytestconfig)
168168

169169
mocker = MockerFixture(pytestconfig)
170170
assert getattr(mocker, name) is getattr(mock_module, name)
@@ -268,6 +268,19 @@ def bar(self, arg):
268268
assert str(spy.spy_exception) == "Error with {}".format(v)
269269

270270

271+
def test_instance_method_spy_autospec_true(mocker: MockerFixture) -> None:
272+
class Foo:
273+
def bar(self, arg):
274+
return arg * 2
275+
276+
foo = Foo()
277+
spy = mocker.spy(foo, "bar")
278+
with pytest.raises(
279+
AttributeError, match="'function' object has no attribute 'fake_assert_method'"
280+
):
281+
spy.fake_assert_method(arg=5)
282+
283+
271284
def test_spy_reset(mocker: MockerFixture) -> None:
272285
class Foo(object):
273286
def bar(self, x):
@@ -342,6 +355,17 @@ def bar(cls, arg):
342355
assert spy.spy_return == 20
343356

344357

358+
@skip_pypy
359+
def test_class_method_spy_autospec_false(mocker: MockerFixture) -> None:
360+
class Foo:
361+
@classmethod
362+
def bar(cls, arg):
363+
return arg * 2
364+
365+
spy = mocker.spy(Foo, "bar")
366+
spy.fake_assert_method()
367+
368+
345369
@skip_pypy
346370
def test_class_method_subclass_spy(mocker: MockerFixture) -> None:
347371
class Base:

0 commit comments

Comments
 (0)