SuperShoot
SuperShoot

Reputation: 10871

Unexpected result: pytest.raises with match parameter when asserting error

Sanity check please!

I'm trying to understand an unexpected test failure when including the exact message returned from an incorrect function call to the match parameter of pytest.raises().

The docs state:

match – if specified, asserts that the exception matches a text or regex

The sequence of instructions in the repl below pretty much says it all, but for some reason the last test fails.

PS C:\Users\peter_000\OneDrive\git\test> pipenv run python
Loading .env environment variables…
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>>
>>>
>>> import pytest
>>> pytest.__version__
'4.4.1'
>>>
>>> with pytest.raises(TypeError, match='a string'):
...     raise TypeError('a string')  # passes
...
>>> def func():
...     pass
...
>>> func(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes 0 positional arguments but 1 was given
>>>
>>>
>>> with pytest.raises(TypeError, match='func() takes 0 positional arguments but 1 was given'):
...     func(None)  # fails
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: func() takes 0 positional arguments but 1 was given

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "C:\Users\peter_000\.virtualenvs\test-_0Fb_hDQ\lib\site-packages\_pytest\python_api.py", line 735, in __exit__
    self.excinfo.match(self.match_expr)
  File "C:\Users\peter_000\.virtualenvs\test-_0Fb_hDQ\lib\site-packages\_pytest\_code\code.py", line 575, in match
    assert 0, "Pattern '{!s}' not found in '{!s}'".format(regexp, self.value)
AssertionError: Pattern 'func() takes 0 positional arguments but 1 was given' not found in 'func() takes 0 positional arguments but 1 was given'
>>>

I thought that perhaps the '()' might mean something in regex that would cause the strings not to match but:

>>> with pytest.raises(TypeError, match='func()'):
...     raise TypeError('func()')

... passes.

Upvotes: 22

Views: 16851

Answers (3)

rchitect-of-info
rchitect-of-info

Reputation: 1596

Just do what the error message says and use re.escape():

with pytest.raises(TypeError, match=re.escape('func()')):
    raise TypeError('func()')

Upvotes: 8

gmdev
gmdev

Reputation: 3155

I have a method that raises a ValueError if too large of an integer is passed into the method. My test method looked like this:

import pytest

def test_func_exceeding_max_value():
    with pytest.raises(ValueError, match="maximum value of (2**32)-1 exceeded"):
        func(2**32)

This test is met with the error:

re.error: multiple repeat at position 20

Position 20 corresponds to this character:

maximum value of (2**32)-1 exceeded
                    ^

As the error suggests, it is raised from an error in a regex expression. What I failed to initially realize is that the match parameter in pytest.raises is a regular expression and not a literal string match. In the pytest documentation, it states:

You can pass a match keyword parameter to the context-manager [pytest.raises] to test that a regular expression matches on the string representation of an exception...

You need to use two backslashes (\\) instead of one before the special characters (, *, ), etc. This is because when the match parameter is sent to the re.match method, the backslash itself needs to be escaped so that re.match interprets that backslash as a literal character and will then escape the correct character:

def test_func_exceeding_max_value():
    with pytest.raises(ValueError, match="maximum value of \\(2\\*\\*32\\)-1 exceeded"):
        func(2**32)

Upvotes: 1

Andrea Corbellini
Andrea Corbellini

Reputation: 17761

Match takes a regular expression pattern, and some characters like () are special. You need to escape them:

>>> with pytest.raises(TypeError, match=r'func\(\) takes 0 positional arguments but 1 was given'):
... #                                   ^     ^^^^
...     func(None)  # succeeds
>>>

The reason why it was failing before is that () in a regular expression corresponds to an empty group, and so your pattern would have matched the string func takes 0 positional arguments but 1 was given.

The reason why match='func()' passes is that the particular regex is looking for func anywhere in the string: it may be followed or preceded by any text.

Upvotes: 26

Related Questions