pytest: Add pytest.raises(..., contains='substring')

pytest.raises can assert the message content as regexp via the match parameter. However, most of the time, I really only need to check if a substring is present in the message. Regexp is quite a heavy machinery for that and I have to constantly think whether my expression is plain text or whether I have to escape literally or via re.escape.

We currently have to do:

with pytest.raises(ValueError, match=r'Unsupported file type: \*\.py')
# or
with pytest.raises(ValueError, match=re.escape('Unsupported file type: *.py'))

I would additionally like to have:

with pytest.raises(ValueError, contains='Unsupported file type: *.py')

On the one hand, this is a bit redundant: You have the functionality with escaped regexes and the two keywords match and contains would be mutually exclusive. On the other hand, contains is much simpler to think about, and I’d claim sufficient for 90% of the use cases.

Question: Would you be open to a PR adding this?

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 29 (29 by maintainers)

Most upvoted comments

fnmatch would be okish but not my preferred solution. Here is why:

I have seen quite some testing code and the majority of use cases really only need contains (i.e. check that some words / a phrase is in the message). fnmatch wildcards are simple and few enough that they don’t complicate that use case. OTOH the few cases that really need flexible matching can well be handled by match. So the added complexity of fnmatch over contains is not needed.

In particular API-wise I find

with pytest.raises(ValueError, contains='...')

more readable than

with pytest.raises(ValueError, fnmatch='...')

I don’t think we should add this or startswith or endswith etc. that would naturally follow

it’s easy to either use match= or what I prefer (directly testing the stringification):

with pytest.raises(ValueError) as excinfo:
    do_thing()
assert 'Unsupported file type: *.py' in str(excinfo.value)
# or an exact check:
# msg, = excinfo.value.args
# assert msg == '...'