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:
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:
=================================== 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.
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:
assert str(e.value) == "Username is properly set"
Here is the full code sample:
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:
@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.
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