Repository navigation
Match causing exceptions with pytest.raises #12763
Description
read more about causing / context exceptions in the python doc
What's the problem this feature will solve?
It is quite unintuitive to match a causing exceptions (raise Exception("i'm an exception") from Exception("I'm the causing exception")) with pytest.
As for an example, imagine you want to match a ValueError with the message "i'm a causing exception"
def i_raise_a_causing_exception():
raise RuntimeError("i'm an exception") from ValueError("i'm a causing exception")If you want to do that now, you have to use the workaround described below.
Describe the solution you'd like
pytest.raises could have an option to match the causing/context exception. Of course, now comes the problem of recursivity since causing/context exceptions can themselves be causing exceptions.
This would allow us to do something along the lines of
def test_catch_cause_exception():
with pytest.raises(ValueError, match="i'm a causing exception", cause=True)if we have multiple caused exception chained together, we could also have:
def i_raise_a_causing_exception():
try:
raise ValueError("i'm the 1st causing exception") from ValueError("i'm the 2nd causing exception")
except ValueError as e:
raise ValueError("i'm an exception") from e
def test_catch_cause_exception():
with pytest.raises(ValueError, match="i'm the 3rd causing exception", cause=2):
i_raise_a_causing_exception()Of course, we would have the same thing for context exceptions:
def i_raise_a_context_exception():
try:
try:
raise ValueError("This is the first context")
finally:
raise ValueError("This is the second context")
finally:
raise ValueError("This is the cause")
def test_catch_context_exception():
with pytest.raises(ValueError, match="This is the second context", context=2):
i_raise_a_context_exception()Alternative Solutions
I just found a workaround in https://stackoverflow.com/a/78939835/12550791 (disclaimer, i'm the author of the question and of the solution).
def test_catch_cause_exception():
# match any exception
with pytest.raises(Exception) as exc:
i_raise_a_causing_exception()
# match the cause exception
with pytest.raises(ValueError, match="i'm a causing exception"):
# in case the first exception caught does not have a cause.
# This will mark the test a failed with `did not raise`.
if exc.value.__cause__ is not None:
raise exc.value.__cause__However, if you have multiple level of cause exceptions you will have to rely on recursivity or make one with the mess.
Activity
The-Compiler commentedon Sep 2, 2024
The
match=argument is a shorthand for the common case. I'm -1 on trying to shoehorn all kinds of special cases in such a shorthand, since what you describe - even more so when considering multiple levels - seems quite exotic.You can always just access the exception object itself and act on that instead:
MarcBresson commentedon Sep 2, 2024
the issue with
assert str(excinfo.value.__cause__) == "i'm a causing exception"is that we do not benefit from the special errordid not raise. Besides, it makes the user use the - very unknown -__cause__or__context__attribute of exceptions.RonnyPfannschmidt commentedon Sep 2, 2024
having a matcher object that expresses this explicit with a explcit api thats not barking up the wrong tree of DWIM is the way to go here, matcher objects are still pre-planning and in early discussion
MarcBresson commentedon Sep 2, 2024
oh! Would love to see matcher objects!
Should I close this issue then?
RonnyPfannschmidt commentedon Sep 2, 2024
We should link it up with the other discussions first
jakkdl commentedon Mar 5, 2025
#13192 allows this with