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 * ``pytest.warns`` now checks for subclass relationship rather than
class equality. Thanks `@lesteve`_ for the PR (`#2166`_) 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 Changes
------- -------
@ -56,6 +59,7 @@ Changes
.. _@fogo: https://github.com/fogo .. _@fogo: https://github.com/fogo
.. _@mandeep: https://github.com/mandeep .. _@mandeep: https://github.com/mandeep
.. _@unsignedint: https://github.com/unsignedint .. _@unsignedint: https://github.com/unsignedint
.. _@Kriechi: https://github.com/Kriechi
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512 .. _#1512: https://github.com/pytest-dev/pytest/issues/1512
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874 .. _#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: >>> with raises(ValueError) as exc_info:
... if value > 10: ... if value > 10:
... raise ValueError("value must be <= 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 Instead, the following approach must be taken (note the difference in
scope):: scope)::
@ -1143,7 +1143,16 @@ def raises(expected_exception, *args, **kwargs):
... if value > 10: ... if value > 10:
... raise ValueError("value must be <= 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:: 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)) raise TypeError(msg % type(expected_exception))
message = "DID NOT RAISE {0}".format(expected_exception) message = "DID NOT RAISE {0}".format(expected_exception)
match_expr = None
if not args: if not args:
if "message" in kwargs: if "message" in kwargs:
message = kwargs.pop("message") 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): elif isinstance(args[0], str):
code, = args code, = args
assert isinstance(code, str) assert isinstance(code, str)
@ -1222,9 +1235,10 @@ def raises(expected_exception, *args, **kwargs):
pytest.fail(message) pytest.fail(message)
class RaisesContext(object): class RaisesContext(object):
def __init__(self, expected_exception, message): def __init__(self, expected_exception, message, match_expr):
self.expected_exception = expected_exception self.expected_exception = expected_exception
self.message = message self.message = message
self.match_expr = match_expr
self.excinfo = None self.excinfo = None
def __enter__(self): def __enter__(self):
@ -1246,6 +1260,8 @@ class RaisesContext(object):
suppress_exception = issubclass(self.excinfo.type, self.expected_exception) suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
if sys.version_info[0] == 2 and suppress_exception: if sys.version_info[0] == 2 and suppress_exception:
sys.exc_clear() sys.exc_clear()
if self.match_expr:
self.excinfo.match(self.match_expr)
return suppress_exception return suppress_exception

View File

@ -118,3 +118,18 @@ class TestRaises:
for o in gc.get_objects(): for o in gc.get_objects():
assert type(o) is not T 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) pytest.deprecated_call(self.dep_explicit, 0)
def test_deprecated_call_as_context_manager_no_warning(self): 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(): with pytest.deprecated_call():
self.dep(1) self.dep(1)
assert str(ex.value).startswith("DID NOT WARN")
def test_deprecated_call_as_context_manager(self): def test_deprecated_call_as_context_manager(self):
with pytest.deprecated_call(): with pytest.deprecated_call():