Work with exceptions using PyTest

Have you ever been wondering how to test your custom Python exceptions? PyTest can help you with this. It has a method that tests for raised exceptions called pytest.raises.

We can use the following simple example:

Copy
import pytest
def test_excetion(name):
    if name!= "Yana":
        raise Exception("Invalid User")
    else:
        return "Username is properly set"def test_username_exception():
    """test that exception is raised for invalid username"""    with pytest.raises(Exception):
       assert test_excetion("Yana")


if __name__ == "__main__":
    pytest.main([__file__, "-k", "test_", "-v", "-s"])

The test_excetion function will check if the username provided is the one expected. If everything is as it has to be, the function will return "Username is properly set"; otherwise, it will raise an exception.

Now we will use pytest.raises to confirm that an exception is actually raised if an invalid username is passed. Running the tests on the code as it is above should fail:

Copy
=================================== FAILURES ===================================
___________________________ test_username_exception ____________________________

    def test_username_exception():
        """test that exception is raised for invalid username"""        with pytest.raises(Exception):
>        assert test_excetion("Yana")
E        Failed: DID NOT RAISE

Perfecto.py:28: Failed
====================== 1 failed, 1 error in 0.01 seconds =======================

If no exception is raised, the test fails. Our test failed as expected with the following message:

Failed: DID NOT RAISE <class 'Exception'>.  

Let's check now if our etest will pass when we raise an exception. To confirm this, we will pass an invalid username. 

Copy
import pytest
def test_excetion(name):
    if name!= "Yana":
        raise Exception("Invalid User")
    else:
        return "Username is properly set"def test_username_exception():
    """test that exception is raised for invalid username"""    with pytest.raises(Exception):
       assert test_excetion("Anna")


if __name__ == "__main__":
    pytest.main([__file__, "-k", "test_", "-v", "-s"])

Here is the result from our execution:

====================== 1 passed, 1 error in 0.01 seconds =======================

Process finished with exit code 0

And if just checking that an exception is raised is not enough, you can check for the text of this exception. To do this, you only need to add one more line at the end of your exception test, as follow:

Copy
assert str(e.value) == "Username is properly set"

Here is the full code sample:

Copy
import pytest
def test_excetion(name):
    if name!= "Yana":
        raise Exception("Invalid User")
    else:
        return "Username is properly set"def test_username_exception():
    """test that exception is raised for invalid username"""    with pytest.raises(Exception):
       assert test_excetion("Anna")
    assert str(e.value) == "Username is properly set"if __name__ == "__main__":
    pytest.main([__file__, "-k", "test_", "-v", "-s"])

It is also possible to specify a “raises” argument to pytest.mark.xfail, which checks that the test is failing in a more specific way than just having any exception raised, as follows:

Copy
@pytest.mark.xfail(raises=IndexError) 
def test_f(): 
    f()

Use cases:

  • pytest.raises is likely to be better for cases where you test exceptions that your own code deliberately raises, 
  • @pytest.mark.xfail with a check function is probably better for something like documenting unfixed bugs (where the test describes what “should” happen) or bugs in dependencies.

Mock an external exception

Following is an example how to mock an exception that could be raised in conditions that are out of your control.

Copy
from unittest import mock
from unittest.mock import patch

import pytest
from requests import HTTPError

from main import make_request


@patch("main.requests")
def test_external_exception(mock_requests):
    exception = HTTPError(mock.Mock(status=404), "not found")
    mock_requests.get(mock.ANY).raise_for_status.side_effect = exception

    with pytest.raises(HTTPError) as error_info:
        make_request()
        assert error_info == exception