Skip to content

Commit

Permalink
allow error message matching in pytest.raises
Browse files Browse the repository at this point in the history
  • Loading branch information
Kriechi committed Feb 2, 2017
1 parent 3b47cb4 commit 6170607
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ New Features
* ``pytest.warns`` now checks for subclass relationship rather than
class equality. Thanks `@lesteve`_ for the PR (`#2166`_)

* ``pytest.raises`` now asserts that the error message matches a text or regex
with the `match` keyword argument. Thanks `@Kriechi`_ for the PR.


Changes
-------
Expand Down Expand Up @@ -56,6 +59,7 @@ Changes
.. _@fogo: https://github.com/fogo
.. _@mandeep: https://github.com/mandeep
.. _@unsignedint: https://github.com/unsignedint
.. _@Kriechi: https://github.com/Kriechi

.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874
Expand Down
27 changes: 23 additions & 4 deletions _pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ def raises(expected_exception, *args, **kwargs):
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
... assert str(exc_info.value) == "value must be <= 10" # this will not execute
... assert exc_info.type == ValueError # this will not execute
Instead, the following approach must be taken (note the difference in
scope)::
Expand All @@ -1143,7 +1143,16 @@ def raises(expected_exception, *args, **kwargs):
... if value > 10:
... raise ValueError("value must be <= 10")
...
>>> assert str(exc_info.value) == "value must be <= 10"
>>> assert exc_info.type == ValueError
Or you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
Or you can specify a callable by passing a to-be-called lambda::
Expand Down Expand Up @@ -1194,11 +1203,15 @@ def raises(expected_exception, *args, **kwargs):
raise TypeError(msg % type(expected_exception))

message = "DID NOT RAISE {0}".format(expected_exception)
match_expr = None

if not args:
if "message" in kwargs:
message = kwargs.pop("message")
return RaisesContext(expected_exception, message)
if "match" in kwargs:
match_expr = kwargs.pop("match")
message += " matching '{0}'".format(match_expr)
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
Expand All @@ -1222,9 +1235,10 @@ def raises(expected_exception, *args, **kwargs):
pytest.fail(message)

class RaisesContext(object):
def __init__(self, expected_exception, message):
def __init__(self, expected_exception, message, match_expr):
self.expected_exception = expected_exception
self.message = message
self.match_expr = match_expr
self.excinfo = None

def __enter__(self):
Expand All @@ -1246,6 +1260,11 @@ def __exit__(self, *tp):
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
if sys.version_info[0] == 2 and suppress_exception:
sys.exc_clear()
if self.match_expr:
try:
self.excinfo.match(self.match_expr)
except:
pytest.fail(self.message)
return suppress_exception


Expand Down
18 changes: 18 additions & 0 deletions testing/python/raises.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,21 @@ def __call__(self):
for o in gc.get_objects():
assert type(o) is not T


def test_raises_match(self):
msg = r"with base \d+"
with pytest.raises(ValueError, match=msg):
int('asdf')

msg = "with base 10"
with pytest.raises(ValueError, match=msg):
int('asdf')

msg = "with base 16"
try:
with pytest.raises(ValueError, match=msg):
int('asdf', base=10)
except pytest.raises.Exception as e:
assert e.msg == "DID NOT RAISE {0} matching '{1}'".format(repr(ValueError), msg)
else:
assert False, "Expected pytest.raises.Exception"
3 changes: 1 addition & 2 deletions testing/test_recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,9 @@ def test_deprecated_explicit_call(self):
pytest.deprecated_call(self.dep_explicit, 0)

def test_deprecated_call_as_context_manager_no_warning(self):
with pytest.raises(pytest.fail.Exception) as ex:
with pytest.raises(pytest.fail.Exception, matches='^DID NOT WARN'):
with pytest.deprecated_call():
self.dep(1)
assert str(ex.value).startswith("DID NOT WARN")

def test_deprecated_call_as_context_manager(self):
with pytest.deprecated_call():
Expand Down

0 comments on commit 6170607

Please sign in to comment.