Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking custom exceptions in Python

Most third-party Python libraries throw custom exceptions. Many of these exceptions have their own dependencies and side effects. Consider, for example, the following situation:

class ThirdPartyException(BaseException):
    def __init__(self):
        print("I do something arcane and expensive upon construction.")
        print("Maybe I have a bunch of arguments that can't be None, too.")

    def state(self) -> bool:
        # In real life, this could be True or False
        return True

Let's say, moreover, that I absolutely have to handle this exception, and to do it, I need to look at the exception's state. If I want to write tests to examine the behavior when this exception is handled, I must have the ability to create a ThirdPartyException. But I may not even be able to figure out how, let alone how to do it cheaply.

If this weren't an Exception and I wanted to write tests, I would immediately reach for MagicMock. But I cannot figure out how to use MagicMock with an exception.

How do I test the error handling cases in the following code, ideally using py.test?

def error_causing_thing():
    raise ThirdPartyException() 

def handle_error_conditionally():
    try:
        error_causing_thing()
    exception ThirdPartyException as e:
        if state:
             return "Some non-error value"
        else:
             return "A different non-error value"
like image 970
David Bruce Borenstein Avatar asked Oct 28 '25 09:10

David Bruce Borenstein


1 Answers

I know this is a stale question, and I am not using pytest, but I had a similar issue with unittest, and just found a solution that someone else may find helpful. I added a patch for my custom exception with new keyword having a value of any class that is a subclass of an Exception (or is the Exception class):

import unittest
from unittest import TestCase
from unittest.mock import patch
# ... other imports required for these unit tests

class MockCustomException(Exception):
    def state(self):
        return self.__class__.state_return_value

class MyTestCase(TestCase):
    def setUp(self):
        custom_exception_patcher = patch(
            'path.to.CustomException',
            new=MockCustomException
        )
        custom_exception_patcher.start()                # start patcher
        self.addCleanup(custom_exception_patcher.stop)  # stop patch after test
    
    def test_when_state_true(self):
        MockCustomException.state_return_value = True
        self.assertEqual(handle_error_conditionally(), "Some non-error value")
    
    def test_when_state_false(self):
        MockCustomException.state_return_value = False
        self.assertEqual(handle_error_conditionally(), "A different non-error value")

This method can also be used on a per test basis, by using patch as a decorator or as a context manager:

# ... imports, etc
class MockCustomExceptionStateTrue:
     def state(self):
         return True

@patch('path.to.CustomException', new=MockCustomExceptionStateTrue)
def test_with_decorator_patch(self):
     self.assertEqual(handle_error_conditionally(), "Some non-error value")

def test_with_context_manager(self):
    
    class MockCustomException:
         def state(self):
            return True
    
    with patch('path.to.CustomException', new=MockCustomException):
         self.assertEqual(handle_error_conditionally(), "Some non-error value")

Hopefully this is helpful for someone!! After briefly looking through pytest docs, it looks like monkeypatching is similar to the unittest patch functionality

like image 107
Brady Perry Avatar answered Oct 31 '25 06:10

Brady Perry



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!