From 4e1e906866681bf6c9202382ebc6c191fb6f3fb5 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 --- src/pytest_mock/plugin.py | 4 ++-- tests/test_pytest_mock.py | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) 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))