Skip to content

Commit

Permalink
Ensure all the exceptions cat be spied on
Browse files Browse the repository at this point in the history
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
  • Loading branch information
webknjaz committed Dec 11, 2020
1 parent 9e1464b commit e5f2216
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 6 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
------------------
Expand Down
4 changes: 2 additions & 2 deletions src/pytest_mock/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
22 changes: 18 additions & 4 deletions tests/test_pytest_mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit e5f2216

Please sign in to comment.