diff --git a/AUTHORS b/AUTHORS index 06947d17b..68298ba80 100644 --- a/AUTHORS +++ b/AUTHORS @@ -52,6 +52,7 @@ Christian Boelsen Christian Theunert Christian Tismer Christopher Gilling +Christopher Dignam CrazyMerlyn Cyrus Maden Dhiren Serai diff --git a/changelog/4536.bugfix.rst b/changelog/4536.bugfix.rst new file mode 100644 index 000000000..0ec84a62b --- /dev/null +++ b/changelog/4536.bugfix.rst @@ -0,0 +1 @@ +``monkeypatch.delattr`` handles class descriptors like ``staticmethod``/``classmethod``. diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 2c81de177..46d9718da 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -181,6 +181,8 @@ class MonkeyPatch(object): attribute is missing. """ __tracebackhide__ = True + import inspect + if name is notset: if not isinstance(target, six.string_types): raise TypeError( @@ -194,7 +196,11 @@ class MonkeyPatch(object): if raising: raise AttributeError(name) else: - self._setattr.append((target, name, getattr(target, name, notset))) + oldval = getattr(target, name, notset) + # Avoid class descriptors like staticmethod/classmethod. + if inspect.isclass(target): + oldval = target.__dict__.get(name, notset) + self._setattr.append((target, name, oldval)) delattr(target, name) def setitem(self, dic, name, value): diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 9e44b4975..0a953d3f1 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -391,6 +391,33 @@ def test_issue156_undo_staticmethod(Sample): assert Sample.hello() +def test_undo_class_descriptors_delattr(): + class SampleParent(object): + @classmethod + def hello(_cls): + pass + + @staticmethod + def world(): + pass + + class SampleChild(SampleParent): + pass + + monkeypatch = MonkeyPatch() + + original_hello = SampleChild.hello + original_world = SampleChild.world + monkeypatch.delattr(SampleParent, "hello") + monkeypatch.delattr(SampleParent, "world") + assert getattr(SampleParent, "hello", None) is None + assert getattr(SampleParent, "world", None) is None + + monkeypatch.undo() + assert original_hello == SampleChild.hello + assert original_world == SampleChild.world + + def test_issue1338_name_resolving(): pytest.importorskip("requests") monkeypatch = MonkeyPatch()