allow error message matching in pytest.raises

This commit is contained in:
Thomas Kriechbaumer 2017-02-01 13:37:13 +01:00
parent 3b47cb45e6
commit 43662ce789
4 changed files with 40 additions and 6 deletions

View File

@ -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
-------
@ -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

View File

@ -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)::
@ -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::
@ -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)
@ -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):
@ -1246,6 +1260,8 @@ class RaisesContext(object):
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:
self.excinfo.match(self.match_expr)
return suppress_exception

View File

@ -118,3 +118,18 @@ class TestRaises:
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"
expr = r"Pattern '{}' not found in 'invalid literal for int\(\) with base 10: 'asdf''".format(msg)
with pytest.raises(AssertionError, match=expr):
with pytest.raises(ValueError, match=msg):
int('asdf', base=10)

View File

@ -112,10 +112,9 @@ class TestDeprecatedCall(object):
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():