Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use getattr_static in spy instead of __getattributes__ #224

Merged
merged 4 commits into from Jan 10, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 8 additions & 14 deletions src/pytest_mock/plugin.py
Expand Up @@ -99,20 +99,14 @@ def spy(self, obj: object, name: str) -> unittest.mock.MagicMock:
:return: Spy object.
"""
method = getattr(obj, name)

autospec = inspect.ismethod(method) or inspect.isfunction(method)
# Can't use autospec classmethod or staticmethod objects
# see: https://bugs.python.org/issue23078
if inspect.isclass(obj):
# Bypass class descriptor:
# http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static
try:
value = obj.__getattribute__(obj, name) # type:ignore
except AttributeError:
pass
else:
if isinstance(value, (classmethod, staticmethod)):
autospec = False
if inspect.isclass(obj) and isinstance(
inspect.getattr_static(obj, name), (classmethod, staticmethod)
):
# Can't use autospec classmethod or staticmethod objects before 3.7
# see: https://bugs.python.org/issue23078
autospec = False
else:
autospec = inspect.ismethod(method) or inspect.isfunction(method)

def wrapper(*args, **kwargs):
spy_obj.spy_return = None
Expand Down
28 changes: 26 additions & 2 deletions tests/test_pytest_mock.py
Expand Up @@ -7,7 +7,7 @@
from unittest.mock import MagicMock

import pytest
from pytest_mock import MockerFixture, PytestMockWarning
from pytest_mock import MockerFixture, PytestMockWarning # type: ignore

pytest_plugins = "pytester"

Expand Down Expand Up @@ -162,7 +162,7 @@ def test_mock_patch_dict_resetall(mocker: MockerFixture) -> None:
],
)
def test_mocker_aliases(name: str, pytestconfig: Any) -> None:
from pytest_mock.plugin import _get_mock_module
from pytest_mock.plugin import _get_mock_module # type: ignore
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the pre-commit mypy run caught these imports with error: Cannot find implementation or library
but now the linting test is failing because of them...any ideas?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, but I've moved _get_mock_module and parse_ini_boolean to a _util.py module: this makes it clear this is not part of the API plus should fix that particular problem.


mock_module = _get_mock_module(pytestconfig)

Expand Down Expand Up @@ -268,6 +268,19 @@ def bar(self, arg):
assert str(spy.spy_exception) == "Error with {}".format(v)


def test_instance_method_spy_autospec_true(mocker: MockerFixture) -> None:
class Foo:
def bar(self, arg):
return arg * 2

foo = Foo()
spy = mocker.spy(foo, "bar")
with pytest.raises(
AttributeError, match="'function' object has no attribute 'fake_assert_method'"
):
spy.fake_assert_method(arg=5)


def test_spy_reset(mocker: MockerFixture) -> None:
class Foo(object):
def bar(self, x):
Expand Down Expand Up @@ -342,6 +355,17 @@ def bar(cls, arg):
assert spy.spy_return == 20


@skip_pypy
def test_class_method_spy_autospec_false(mocker: MockerFixture) -> None:
class Foo:
@classmethod
def bar(cls, arg):
return arg * 2

spy = mocker.spy(Foo, "bar")
spy.fake_assert_method()


@skip_pypy
def test_class_method_subclass_spy(mocker: MockerFixture) -> None:
class Base:
Expand Down