Skip to content

Feature request: refactor the pytest.raises interface #4538

@wimglenn

Description

@wimglenn

Interface for pytest.raises is a stumbling block. Problems:

1. The "message" kwarg violates principle of least surprise. User probably expected: simple match on exception message (useful), but it's actually for providing custom failure message (not very useful). Recommend deprecation. message has now been deprecated.
2. The "match" kwarg violates principle of least surprise. It will catch substrings, for example. And it's annoying when the error message itself contains regex syntax that you'll need to escape. Could possibly be renamed to "regexp" or something? ...don't care / don't use it
3. No easy way to achieve the most simple and basic use-case: assert exact match on an exception instance args with basic types such as strings and/or numbers. use the plugin instead: pytest-raisin

Here are some examples of problematic usage.

# looks right, but doesn't assert what it seems like it's asserting. trappy for beginners!
with pytest.raises(Exception, message="boom"):
    Exception("boom")

# whoops!
with pytest.raises(Exception, message="boom"):
    Exception("bang")

# whoops again! still not there yet..
with pytest.raises(Exception, match="boom"):
    Exception("kaboom")

# correct usage
with pytest.raises(Exception, match="^boom$"):
    Exception("kaboom")

What if the thing you want to match is not really a string? Can encounter this frequently when testing the CLI parts of code (since argparse is gonna raise SystemExit with an integer return code). Problems:

# passes, but not why you thought
with pytest.raises(SystemExit, match=0):
    sys.exit(0)

# passes??
with pytest.raises(SystemExit, match=0):
    sys.exit(1)

# crashes, but crashes too late (`TypeError` in re compile)
with pytest.raises(SystemExit, match=1):
    sys.exit(1)

# correct usage
with pytest.raises(SystemExit) as cm:
    sys.exit(0)
assert cm.value.args == (0,)

# another trap! d'oh!
with pytest.raises(SystemExit) as cm:
    sys.exit(0)
    assert cm.value.args == (0,)

I would like to propose using exception instances to overcome these stumbling blocks.

Example usage:

with pytest.raises(SystemExit(2)):
    sys.exit(2)

testfixtures.ShouldRaise is an example of this usage, and I'd love to have that interface in pytest directly. The comparison objects which make it work are non-trivial. A much easier to implement alternative, though less beautiful, might be just to support matching the args attribute as tuple.

with pytest.raises(SomeError, exc_args=(2, "code two")):
    raise SomeError(2, "code two")

Admittedly it won't help people who put too much logic into their custom exception classes, but it should cover majority standard use-cases (where exceptions are dumb subclasses, just with a custom numeric error code or message).

What do you think?

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: proposalproposal for a new feature, often to gather opinions or design the API around the new feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions