Skip to content

Commit bc904f0

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

File tree

4 files changed

+44
-6
lines changed

4 files changed

+44
-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: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" Python test discovery, setup and run of test functions. """
22

3+
import re
34
import fnmatch
45
import inspect
56
import sys
@@ -1134,7 +1135,7 @@ def raises(expected_exception, *args, **kwargs):
11341135
>>> with raises(ValueError) as exc_info:
11351136
... if value > 10:
11361137
... raise ValueError("value must be <= 10")
1137-
... assert str(exc_info.value) == "value must be <= 10" # this will not execute
1138+
... assert exc_info.type == ValueError # this will not execute
11381139
11391140
Instead, the following approach must be taken (note the difference in
11401141
scope)::
@@ -1143,7 +1144,16 @@ def raises(expected_exception, *args, **kwargs):
11431144
... if value > 10:
11441145
... raise ValueError("value must be <= 10")
11451146
...
1146-
>>> assert str(exc_info.value) == "value must be <= 10"
1147+
>>> assert exc_info.type == ValueError
1148+
1149+
Or you can use the keyword argument ``match`` to assert that the
1150+
exception matches a text or regex::
1151+
1152+
>>> with raises(ValueError, match='must be 0 or None'):
1153+
... raise ValueError("value must be 0 or None")
1154+
1155+
>>> with raises(ValueError, match=r'must be \d+$'):
1156+
... raise ValueError("value must be 42")
11471157
11481158
11491159
Or you can specify a callable by passing a to-be-called lambda::
@@ -1194,11 +1204,15 @@ def raises(expected_exception, *args, **kwargs):
11941204
raise TypeError(msg % type(expected_exception))
11951205

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

11981209
if not args:
11991210
if "message" in kwargs:
12001211
message = kwargs.pop("message")
1201-
return RaisesContext(expected_exception, message)
1212+
if "match" in kwargs:
1213+
match_expr = kwargs.pop("match")
1214+
message += " matching '{0}'".format(match_expr)
1215+
return RaisesContext(expected_exception, message, match_expr)
12021216
elif isinstance(args[0], str):
12031217
code, = args
12041218
assert isinstance(code, str)
@@ -1222,9 +1236,10 @@ def raises(expected_exception, *args, **kwargs):
12221236
pytest.fail(message)
12231237

12241238
class RaisesContext(object):
1225-
def __init__(self, expected_exception, message):
1239+
def __init__(self, expected_exception, message, match_expr):
12261240
self.expected_exception = expected_exception
12271241
self.message = message
1242+
self.match_expr = match_expr
12281243
self.excinfo = None
12291244

12301245
def __enter__(self):
@@ -1243,6 +1258,8 @@ def __exit__(self, *tp):
12431258
exc_type, value, traceback = tp
12441259
tp = exc_type, exc_type(value), traceback
12451260
self.excinfo.__init__(tp)
1261+
if self.match_expr and not re.search(self.match_expr, str(self.excinfo)):
1262+
pytest.fail(self.message)
12461263
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
12471264
if sys.version_info[0] == 2 and suppress_exception:
12481265
sys.exc_clear()

testing/python/raises.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,21 @@ 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+
try:
133+
with pytest.raises(ValueError, match=msg):
134+
int('asdf', base=10)
135+
except pytest.raises.Exception as e:
136+
assert e.msg == "DID NOT RAISE {0} matching '{1}'".format(repr(ValueError), msg)
137+
else:
138+
assert False, "Expected pytest.raises.Exception"

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') as ex:
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)