Explain Codes LogoExplain Codes Logo

How do you test that a Python function throws an exception?

python
exception-handling
unittest
test-automation
Alex KataevbyAlex Kataev·Aug 18, 2024
TLDR

In Python, testing if a function throws the expected exception involves wrapping your function call within a context manager block, using either with pytest.raises(ExpectedException): for pytest or with self.assertRaises(ExpectedException): for unittest. This asserts that the expected exception is being raised.

Example with pytest:

def test_my_function_raises_exception(): with pytest.raises(ValueError): # This function call should fail, otherwise our test would fail. function_that_should_fail()

Example with unittest:

import unittest class MyTestCase(unittest.TestCase): def test_my_function_raises_exception(self): with self.assertRaises(ValueError): # Optimistic? Expect a ValueError...but hope for the best? function_that_should_fail()

Simply pass the expected exception to either pytest.raises or unittest.assertRaises and execute your function within the block. Should the function not raise the expected exception, then your test will fail.

Expanding on unittest.assertRaises

When writing tests in Python, checking for appropriate exception handling is vital for ensuring the robustness and safety of your code. Python's unittest provides the assertRaises method, allowing you to confirm the occurrence of a specific exception.

Elegant exception test with Context Managers

From Python 3.5 onward, a more elegant approach is possible using context managers:

def test_exception_raising(self): with self.assertRaises(SomeCoolException) as context: # Brace yourself, exception is coming mymod.myfunc() # Is your exception saying what it's supposed to say? self.assertEqual(str(context.exception), 'Exception message')

This example tests not only for the expected SomeCoolException but also verifies the exception message for accuracy.

Asserting exception details

assertRaises can also perform additional checks within the context block to verify exact exception content:

with self.assertRaises(SomeCoolException) as cm: mymod.myfunc() # It's not just any exception, it's an exception carrying the meaning of life! self.assertEqual(cm.exception.error_code, 42)

This snippet tests that your function mymod.myfunc() raises SomeCoolException with an error_code attribute of 42.

Handling older Python versions

For Python 2.7 or 3.1 users and earlier, who lack support for context managers, a try-except block will suffice:

def test_exception_for_old_python(self): try: mymod.myfunc() # Did the exception take a holiday? self.fail('SomeCoolException not raised') except SomeCoolException as e: # We speak Python, not gibberish! self.assertEqual(str(e), 'specific message') except Exception as e: # Who invited the unexpected exception? self.fail(f'Unexpected exception raised: {e}')

In this case, self.fail() will flag the test as an error if no exception is thrown or if the incorrect exception is thrown.

Maintaining assertRaises nuances in older Python

For Python 2.6 users, you can still mimic assertRaises through the unittest2 library.

Running tests with more verbosity and interactive shell

Consider using unittest.TextTestRunner(verbosity=2) for a more detailed test output, which can simplify debugging and enhance your understanding of test outcomes.

Would you like to keep the shell open after the tests run? unittest.main(exit=False) allows you to do that.

The importance of detailed exception testing

In-depth testing and understanding of exceptions verifies your code's ability to correctly signpost when things go wrong. Confirming exception handling isn't just about catching an exception but understanding its contents and origin.

Writing maintainable tests: The A-Z pillar of code quality

When designing your tests, readability is key. They should serve as an accessible documentation of your code. Exception tests with clarity and consistency are hallmarks of quality code and facilitate future maintenance.

Why robustness matters

Verifying exception handling in your tests confirms the robustness of the function under review. It provides insights on how effectively the function handles failure scenarios and can prevent unforeseen issues in production environments.

Key takeaways

  • TestCase.failUnlessRaises is another way to say assertRaises.
  • Linking directly to the assertRaises documentation within your tests can be instrumental for others stepping on the bug-checking journey.
  • Clean and easy-to-follow code snippets maximize learnability and allow for practical applications.