From e5f2216e2555c374fbb1417d7538000901e1f442 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Fri, 11 Dec 2020 11:59:23 +0100 Subject: [PATCH] Ensure all the exceptions cat be spied on This change replaces `Exception` with `BaseException` in the `spy()` method provided by the `mocker` fixture. This enables the caller to spy on things like `KeyboardInterrupt`, `GeneratorExit` and `SystemExit` exceptions that hasn't been possible before because of a bug. Before this change, any occurances of the above exceptions caused `spy()` to assign `None` to both `spy_return` and `spy_exception` attributes. Fixes #215 --- CHANGELOG.rst | 12 ++++++++++++ src/pytest_mock/plugin.py | 4 ++-- tests/test_pytest_mock.py | 22 ++++++++++++++++++---- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 04a08da..87344c5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,8 +3,20 @@ * Add `mock.seal` alias to the `mocker` fixture (`#211`_). Thanks `@coiax`_ for the PR. +* Fixed spying on exceptions not covered by the ``Exception`` + superclass (`#215`_), like ``KeyboardInterrupt`` -- PR `#216`_ + by `@webknjaz`_. + + Before the fix, both ``spy_return`` and ``spy_exception`` + whenever such an exception happened. And after this fix, + ``spy_exception`` is set to a correct value of an exception + that has actually happened. + .. _@coiax: https://github.com/coiax +.. _@webknjaz: https://github.com/sponsors/webknjaz .. _#211: https://github.com/pytest-dev/pytest-mock/pull/211 +.. _#215: https://github.com/pytest-dev/pytest-mock/pull/215 +.. _#216: https://github.com/pytest-dev/pytest-mock/pull/216 3.3.1 (2020-08-24) ------------------ diff --git a/src/pytest_mock/plugin.py b/src/pytest_mock/plugin.py index c9f3853..10aa803 100644 --- a/src/pytest_mock/plugin.py +++ b/src/pytest_mock/plugin.py @@ -114,7 +114,7 @@ def wrapper(*args, **kwargs): spy_obj.spy_exception = None try: r = method(*args, **kwargs) - except Exception as e: + except BaseException as e: spy_obj.spy_exception = e raise else: @@ -126,7 +126,7 @@ async def async_wrapper(*args, **kwargs): spy_obj.spy_exception = None try: r = await method(*args, **kwargs) - except Exception as e: + except BaseException as e: spy_obj.spy_exception = e raise else: diff --git a/tests/test_pytest_mock.py b/tests/test_pytest_mock.py index 33a80b2..7650bb7 100644 --- a/tests/test_pytest_mock.py +++ b/tests/test_pytest_mock.py @@ -2,7 +2,7 @@ import platform import sys from contextlib import contextmanager -from typing import Callable, Any, Tuple, Generator +from typing import Callable, Any, Tuple, Generator, Type from unittest.mock import MagicMock import pytest @@ -235,17 +235,31 @@ def bar(self, arg): assert spy.spy_return == 20 -def test_instance_method_spy_exception(mocker: MockerFixture) -> None: +# Ref: https://docs.python.org/3/library/exceptions.html#exception-hierarchy +@pytest.mark.parametrize( + "exc_cls", + ( + BaseException, + Exception, + GeneratorExit, # BaseException + KeyboardInterrupt, # BaseException + RuntimeError, # regular Exception + SystemExit, # BaseException + ), +) +def test_instance_method_spy_exception( + exc_cls: Type[BaseException], mocker: MockerFixture, +) -> None: class Foo: def bar(self, arg): - raise Exception("Error with {}".format(arg)) + raise exc_cls("Error with {}".format(arg)) foo = Foo() spy = mocker.spy(foo, "bar") expected_calls = [] for i, v in enumerate([10, 20]): - with pytest.raises(Exception, match="Error with {}".format(v)) as exc_info: + with pytest.raises(exc_cls, match="Error with {}".format(v)) as exc_info: foo.bar(arg=v) expected_calls.append(mocker.call(arg=v))