Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use pytest to check that Error is NOT raised

A test will fail if it raises any kind of unexpected Exception. You can just invoke foo(7) and you will have tested that no MyError is raised. So, following will suffice:

def test_foo3():
    foo(7)

If you want to be explicit and write an assert statement for this, you can do:

def test_foo3():
    try:
        foo(7)
    except MyError:
        pytest.fail("Unexpected MyError ..")

Building on top of what Oisin mentioned..

You can make a simple not_raises function that acts similar to pytest's raises:

from contextlib import contextmanager

@contextmanager
def not_raises(exception):
  try:
    yield
  except exception:
    raise pytest.fail("DID RAISE {0}".format(exception))

This is fine if you want to stick to having raises having a counterpart and thus your tests being more readable. In essence however you don't really need anything than just running the block of code you want to test on its own line - pytest will fail anyway as soon as that block raises an error.


Since this question was answered, the pytest docs have updated info on this subject that's worth mentioning here.

https://docs.pytest.org/en/6.2.x/example/parametrize.html#parametrizing-conditional-raising

It's similar to some of the other answers, but using parametrize and a newer builtin nullcontext that makes the solution really clean.

A potential Python3.7+ only example would look like:

from contextlib import nullcontext as does_not_raise
import pytest


@pytest.mark.parametrize(
    "example_input,expectation",
    [
        (3, does_not_raise()),
        (2, does_not_raise()),
        (1, does_not_raise()),
        (0, pytest.raises(ZeroDivisionError)),
    ],
)
def test_division(example_input, expectation):
    """Test how much I know division."""
    with expectation:
        assert (6 / example_input) is not None

Using parametrize this way makes it possible to combine OP's test cases, like:

@pytest.mark.parametrize(
    "example_input,expectation,message",
    [
        (3, pytest.raises(MyError), ERROR1),
        (11, pytest.raises(MyError), ERROR2),
        (7, does_not_raise(), None),
    ],
)
def test_foo(example_input, expectation, message):
    with expectation as e:
        foo(example_input)
    assert message is None or message in str(e)

Doing it this way allows you to test that it did not raise any exception. nullcontext is meant as a stand in for an optional context manager (pytest.raises, in this case). It's not actually doing anything, so if you wanted to test that it did NOT raise a specific exception, you should see one of the other answers.


I was curious to see if a not_raises would work. A quick test of this is (test_notraises.py):

from contextlib import contextmanager

@contextmanager
def not_raises(ExpectedException):
    try:
        yield

    except ExpectedException, err:
        raise AssertionError(
            "Did raise exception {0} when it should not!".format(
                repr(ExpectedException)
            )
        )

    except Exception, err:
        raise AssertionError(
            "An unexpected exception {0} raised.".format(repr(err))
        )

def good_func():
    print "hello"


def bad_func():
    raise ValueError("BOOM!")


def ugly_func():
    raise IndexError("UNEXPECTED BOOM!")


def test_ok():
    with not_raises(ValueError):
        good_func()


def test_bad():
    with not_raises(ValueError):
        bad_func()


def test_ugly():
    with not_raises(ValueError):
        ugly_func()

It does seem to work. However I'm not sure if it really reads well in the test.