diff --git a/pytest_mock.py b/pytest_mock.py index 63e2a82..bc6b411 100644 --- a/pytest_mock.py +++ b/pytest_mock.py @@ -60,11 +60,15 @@ def spy(self, obj, name): # Can't use autospec classmethod or staticmethod objects # see: https://bugs.python.org/issue23078 if inspect.isclass(obj): - # bypass class descriptor: + # Bypass class descriptor: # http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static - value = obj.__getattribute__(obj, name) - if isinstance(value, (classmethod, staticmethod)): - autospec = False + try: + value = obj.__getattribute__(obj, name) + except AttributeError: + pass + else: + if isinstance(value, (classmethod, staticmethod)): + autospec = False result = self.patch.object(obj, name, side_effect=method, autospec=autospec) diff --git a/test_pytest_mock.py b/test_pytest_mock.py index 703d165..bfefccb 100644 --- a/test_pytest_mock.py +++ b/test_pytest_mock.py @@ -211,6 +211,27 @@ def bar(self, arg): assert spy.call_args_list == calls +@skip_pypy +def test_instance_method_by_subclass_spy(mocker): + from pytest_mock import mock_module + + class Base(object): + + def bar(self, arg): + return arg * 2 + + class Foo(Base): + pass + + spy = mocker.spy(Foo, 'bar') + foo = Foo() + other = Foo() + assert foo.bar(arg=10) == 20 + assert other.bar(arg=10) == 20 + calls = [mock_module.call(foo, arg=10), mock_module.call(other, arg=10)] + assert spy.call_args_list == calls + + @skip_pypy def test_class_method_spy(mocker): class Foo(object): @@ -225,6 +246,23 @@ def bar(cls, arg): spy.assert_called_once_with(arg=10) +@skip_pypy +def test_class_method_subclass_spy(mocker): + class Base(object): + + @classmethod + def bar(self, arg): + return arg * 2 + + class Foo(Base): + pass + + spy = mocker.spy(Foo, 'bar') + assert Foo.bar(arg=10) == 20 + Foo.bar.assert_called_once_with(arg=10) + spy.assert_called_once_with(arg=10) + + @skip_pypy def test_class_method_with_metaclass_spy(mocker): class MetaFoo(type): @@ -258,6 +296,23 @@ def bar(arg): spy.assert_called_once_with(arg=10) +@skip_pypy +def test_static_method_subclass_spy(mocker): + class Base(object): + + @staticmethod + def bar(arg): + return arg * 2 + + class Foo(Base): + pass + + spy = mocker.spy(Foo, 'bar') + assert Foo.bar(arg=10) == 20 + Foo.bar.assert_called_once_with(arg=10) + spy.assert_called_once_with(arg=10) + + @contextmanager def assert_traceback(): """