Skip to content

Commit 43662ce

Browse files
committed
allow error message matching in pytest.raises
1 parent 3b47cb4 commit 43662ce

File tree

4 files changed

+40
-6
lines changed

4 files changed

+40
-6
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ New Features
1313
* ``pytest.warns`` now checks for subclass relationship rather than
1414
class equality. Thanks `@lesteve`_ for the PR (`#2166`_)
1515

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

1720
Changes
1821
-------
@@ -56,6 +59,7 @@ Changes
5659
.. _@fogo: https://github.com/fogo
5760
.. _@mandeep: https://github.com/mandeep
5861
.. _@unsignedint: https://github.com/unsignedint
62+
.. _@Kriechi: https://github.com/Kriechi
5963

6064
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
6165
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874

_pytest/python.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,7 @@ def raises(expected_exception, *args, **kwargs):
11341134
>>> with raises(ValueError) as exc_info:
11351135
... if value > 10:
11361136
... raise ValueError("value must be <= 10")
1137-
... assert str(exc_info.value) == "value must be <= 10" # this will not execute
1137+
... assert exc_info.type == ValueError # this will not execute
11381138
11391139
Instead, the following approach must be taken (note the difference in
11401140
scope)::
@@ -1143,7 +1143,16 @@ def raises(expected_exception, *args, **kwargs):
11431143
... if value > 10:
11441144
... raise ValueError("value must be <= 10")
11451145
...
1146-
>>> assert str(exc_info.value) == "value must be <= 10"
1146+
>>> assert exc_info.type == ValueError
1147+
1148+
Or you can use the keyword argument ``match`` to assert that the
1149+
exception matches a text or regex::
1150+
1151+
>>> with raises(ValueError, match='must be 0 or None'):
1152+
... raise ValueError("value must be 0 or None")
1153+
1154+
>>> with raises(ValueError, match=r'must be \d+$'):
1155+
... raise ValueError("value must be 42")
11471156
11481157
11491158
Or you can specify a callable by passing a to-be-called lambda::
@@ -1194,11 +1203,15 @@ def raises(expected_exception, *args, **kwargs):
11941203
raise TypeError(msg % type(expected_exception))
11951204

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

11981208
if not args:
11991209
if "message" in kwargs:
12001210
message = kwargs.pop("message")
1201-
return RaisesContext(expected_exception, message)
1211+
if "match" in kwargs:
1212+
match_expr = kwargs.pop("match")
1213+
message += " matching '{0}'".format(match_expr)
1214+
return RaisesContext(expected_exception, message, match_expr)
12021215
elif isinstance(args[0], str):
12031216
code, = args
12041217
assert isinstance(code, str)
@@ -1222,9 +1235,10 @@ def raises(expected_exception, *args, **kwargs):
12221235
pytest.fail(message)
12231236

12241237
class RaisesContext(object):
1225-
def __init__(self, expected_exception, message):
1238+
def __init__(self, expected_exception, message, match_expr):
12261239
self.expected_exception = expected_exception
12271240
self.message = message
1241+
self.match_expr = match_expr
12281242
self.excinfo = None
12291243

12301244
def __enter__(self):
@@ -1246,6 +1260,8 @@ def __exit__(self, *tp):
12461260
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
12471261
if sys.version_info[0] == 2 and suppress_exception:
12481262
sys.exc_clear()
1263+
if self.match_expr:
1264+
self.excinfo.match(self.match_expr)
12491265
return suppress_exception
12501266

12511267

testing/python/raises.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,18 @@ def __call__(self):
118118
for o in gc.get_objects():
119119
assert type(o) is not T
120120

121+
122+
def test_raises_match(self):
123+
msg = r"with base \d+"
124+
with pytest.raises(ValueError, match=msg):
125+
int('asdf')
126+
127+
msg = "with base 10"
128+
with pytest.raises(ValueError, match=msg):
129+
int('asdf')
130+
131+
msg = "with base 16"
132+
expr = r"Pattern '{}' not found in 'invalid literal for int\(\) with base 10: 'asdf''".format(msg)
133+
with pytest.raises(AssertionError, match=expr):
134+
with pytest.raises(ValueError, match=msg):
135+
int('asdf', base=10)

testing/test_recwarn.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,9 @@ def test_deprecated_explicit_call(self):
112112
pytest.deprecated_call(self.dep_explicit, 0)
113113

114114
def test_deprecated_call_as_context_manager_no_warning(self):
115-
with pytest.raises(pytest.fail.Exception) as ex:
115+
with pytest.raises(pytest.fail.Exception, matches='^DID NOT WARN'):
116116
with pytest.deprecated_call():
117117
self.dep(1)
118-
assert str(ex.value).startswith("DID NOT WARN")
119118

120119
def test_deprecated_call_as_context_manager(self):
121120
with pytest.deprecated_call():

0 commit comments

Comments
 (0)