I have created my custom exceptions as such within errors.py
mapper = {
'E101':
'There is no data at all for these constraints',
'E102':
'There is no data for these constraints in this market, try changing market',
'E103':
'There is no data for these constraints during these dates, try changing dates',
}
class DataException(Exception):
def __init__(self, code):
super().__init__()
self.msg = mapper[code]
def __str__(self):
return self.msg
Another function somewhere else in the code raises different instances of DataException
if there is not enough data in a pandas
dataframe. I want to use unittest
to ensure that it returns the appropriate exception with its corresponding message.
Using a simple example, why does this not work:
from .. import DataException
def foobar():
raise DataException('E101')
import unittest
with unittest.TestCase.assertRaises(DataException):
foobar()
As suggested here: Python assertRaises on user-defined exceptions
I get this error:
TypeError: assertRaises() missing 1 required positional argument: 'expected_exception'
Or alternatively:
def foobar():
raise DataException('E101')
import unittest
unittest.TestCase.assertRaises(DataException, foobar)
results in:
TypeError: assertRaises() arg 1 must be an exception type or tuple of exception types
Why is it not recognizing DataException
as an Exception
? Why does the linked stackoverflow question answer work without supplying a second argument to assertRaises
?
You are trying to use methods of the TestCase
class without creating an instance; those methods are not designed to be used in that manner.
unittest.TestCase.assertRaises
is an unbound method. You'd use it in a test method on a TestCase
class you define:
class DemoTestCase(unittest.TestCase):
def test_foobar(self):
with self.assertRaises(DataException):
foobar()
The error is raised because unbound methods do not get self
passed in. Because unittest.TestCase.assertRaises
expects both self
and a second argument named expected_exception
you get an exception as DataException
is passed in as the value for self
.
You do now have to use a test runner to manage your test cases; add
if __name__ == '__main__':
unittest.main()
at the bottom and run your file as a script. Your test cases are then auto-discovered and executed.
It is technically possible to use the assertions outside such an environment, see Is there a way to use Python unit test assertions outside of a TestCase?, but I recommend you stick to creating test cases instead.
To further verify the codes and message on the raised exception, assign the value returned when entering the context to a new name with with ... as <target>:
; the context manager object captures the raised exception so you can make assertions about it:
with self.assertRaises(DataException) as context:
foobar()
self.assertEqual(context.exception.code, 'E101')
self.assertEqual(
context.exception.msg,
'There is no data at all for these constraints')
See the TestCase.assertRaises()
documentation.
Last but not least, consider using subclasses of DataException
rather than use separate error codes. That way your API users can just catch one of those subclasses to handle a specific error code, rather than having to do additional tests for the code and re-raise if a specific code should not have been handled there.