4

I have a function to catch uncaught exceptions, below. Is there any way to write a unit test that will execute the uncaught_exception_handler() function, but exit the test normally?

import logging

def config_logger():
    # logger setup here

def init_uncaught_exception_logger(logger):
    '''Setup an exception handler to log uncaught exceptions.

    This is typically called once per main executable.
    This function only exists to provide a logger context to the nested function.

    Args:
        logger (Logger): The logger object to log uncaught exceptions with.
    '''
    def uncaught_exception_handler(*exc_args):
        '''Log uncaught exceptions with logger.

        Args:
            exc_args: exception type, value, and traceback
        '''
        print("Triggered uncaught_exception_handler")
        logger.error("uncaught: {}: {}\n{}".format(*exc_args))

    sys.excepthook = uncaught_exception_handler

if __name__ == '__main__':
    LOGGER = config_logger()
    init_uncaught_exception_logger(LOGGER)
    raise Exception("This is an intentional uncaught exception")
Quantum Mechanic
  • 625
  • 1
  • 6
  • 20
  • Possible duplicate of [How to properly assert that an exception gets raised in pytest?](https://stackoverflow.com/questions/23337471/how-to-properly-assert-that-an-exception-gets-raised-in-pytest) – cody Nov 22 '18 at 16:05
  • @cody: please reread the question? "Uncaught" is the distinguishing factor here. – Quantum Mechanic Nov 23 '18 at 18:12

2 Answers2

3

Instead of testing that your function is called for uncaught exceptions, it's probably best to instead test that the excepthook is installed, and that the function does the right thing when you call it manually. That gives you pretty good evidence that the excepthook will behave properly in real usage. You'll want to move your uncaught_exception_handler outside of init_uncaught_exception_logger so your tests can access it more easily.

assert sys.excepthook is uncaught_exception_handler
with your_preferred_output_capture_mechanism:
    try:
        1/0
    except ZeroDivisionError:
        uncaught_exception_handler(*sys.exc_info())
assert_something_about_captured_output()

If you want to actually invoke excepthook through an uncaught exception, then you'll need to launch a subprocess and examine its output. The subprocess module is the way to go for that.

user2357112
  • 260,549
  • 28
  • 431
  • 505
-1

In order to write assertions about raised exceptions, you can use pytest.raises as a context manager like this:

with raises(expected_exception: Exception[, match][, message])

import pytest

def test_which_will_raise_exception():
    with pytest.raises(Exception):
        # Your function to test.

Now, this unit test will pass only if any code under pytest.raises context manager will raise an exception provided as a parameter. In this case, it's Exception.

Raoslaw Szamszur
  • 1,723
  • 12
  • 21
  • See [sys.excepthook](https://docs.python.org/3.7/library/sys.html?highlight=sys%20excepthook#sys.excepthook), specifically: "_When an exception is raised and **uncaught**, the interpreter calls sys.excepthook..._". Does `pytest.raises()` catch the exception after `sys.excepthook`? – Quantum Mechanic Nov 22 '18 at 15:35
  • 2
    @QuantumMechanic If the call comes from tested code inside `pytest.raises` context manager then it should. Give me some time, I'll do some research and tests. If this will work then I'll provide a more detailed example. – Raoslaw Szamszur Nov 22 '18 at 15:59