38

I'm testing code where one of two exceptions can be raised: MachineError or NotImplementedError. I would like to use pytest.raises to make sure that at least one of them is raised when I run my test code, but it only seems to accept one exception type as an argument.

This is the signature for pytest.raises:

raises(expected_exception, *args, **kwargs)

I tried using the or keyword inside a context manager:

with pytest.raises(MachineError) or pytest.raises(NotImplementedError):
    verb = Verb("donner<IND><FUT><REL><SG><1>")
    verb.conjugate()

but I assume this only checks whether the first pytest.raises is None and sets the second one as the context manager if it is.

Passing multiple exceptions as positional arguments doesn't work, because pytest.raises takes its second argument to be a callable. Every subsequent positional argument is passed as an argument to that callable.

From the documentation:

>>> raises(ZeroDivisionError, lambda: 1/0)
<ExceptionInfo ...>

>>> def f(x): return 1/x
...
>>> raises(ZeroDivisionError, f, 0)
<ExceptionInfo ...>
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>

Passing the exceptions as a list doesn't work either:

Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    with pytest.raises([MachineError, NotImplementedError]):
  File "/usr/local/lib/python3.4/dist-packages/_pytest/python.py", line 1290, in raises
    raise TypeError(msg % type(expected_exception))
TypeError: exceptions must be old-style classes or derived from BaseException, not <class 'list'>

Is there a workaround for this? It doesn't have to use a context manager.

Quentin Pradet
  • 4,691
  • 2
  • 29
  • 41
fenceop
  • 1,439
  • 3
  • 18
  • 29
  • 1
    Did you try passing multiple exceptions to raises, either as positional arguments or a tuple? – jonrsharpe Jul 15 '16 at 19:26
  • @jonrsharpe I tried passing multiple exceptions with a list, not a tuple. [cxw's answer](http://stackoverflow.com/a/38403866/2202669) works. – fenceop Jul 15 '16 at 19:37

1 Answers1

71

Pass the exceptions as a tuple to raises:

with pytest.raises( (MachineError, NotImplementedError) ):
    verb = ...

In the source for pytest, pytest.raises may:

In Python 3, except statements can take a tuple of exceptions. The issubclass function can also take a tuple. Therefore, using a tuple should be acceptable in either situation.

cxw
  • 16,685
  • 2
  • 45
  • 81
  • 1
    Any idea why exceptions can be passed as a tuple but not a list? I read the docs but I don't understand why lists aren't allowed even though they're iterable. – fenceop Jul 15 '16 at 19:46
  • 2
    @fenceop http://stackoverflow.com/questions/35851782/why-does-handling-multiple-exceptions-require-a-tuple-and-not-a-list – cxw Jul 15 '16 at 19:48
  • Although in general from a test perspective, I think this should be disallowed. It's almost like saying: assert 1 + 2 == 3 or 4 – Jonathan Nazario Apr 17 '19 at 01:07
  • 1
    @JonathanNazario: Disagree. Sometimes it is better to tell in the tests what you really want, not what you implemented. If you are fine with both errors to be raised (e.g. because it just depends on the implementation order), you should not implement a stricter test just by knowing the implementation. This is equivalently true for values. If you just require the function returns any element from a set of candidates, do not check against one specific element. – matheburg Jun 14 '22 at 08:25