How do you test that a Python function throws an exception?
-
02-07-2019 - |
Question
How does one write a unittest that fails only if a function doesn't throw an expected exception?
Solution
Use TestCase.assertRaises
(or TestCase.failUnlessRaises
) from the unittest module, for example:
import mymod
class MyTestCase(unittest.TestCase):
def test1(self):
self.assertRaises(SomeCoolException, mymod.myfunc)
OTHER TIPS
Since Python 2.7 you can use context manager to get ahold of the actual Exception object thrown:
import unittest
def broken_function():
raise Exception('This is broken')
class MyTestCase(unittest.TestCase):
def test(self):
with self.assertRaises(Exception) as context:
broken_function()
self.assertTrue('This is broken' in context.exception)
if __name__ == '__main__':
unittest.main()
http://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertRaises
In Python 3.5, you have to wrap context.exception
in str
, otherwise you'll get a TypeError
self.assertTrue('This is broken' in str(context.exception))
The code in my previous answer can be simplified to:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction)
And if afunction takes arguments, just pass them into assertRaises like this:
def test_afunction_throws_exception(self):
self.assertRaises(ExpectedException, afunction, arg1, arg2)
How do you test that a Python function throws an exception?
How does one write a test that fails only if a function doesn't throw an expected exception?
Short Answer:
Use the self.assertRaises
method as a context manager:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
Demonstration
The best practice approach is fairly easy to demonstrate in a Python shell.
The unittest
library
In Python 2.7 or 3:
import unittest
In Python 2.6, you can install a backport of 2.7's unittest
library, called unittest2, and just alias that as unittest
:
import unittest2 as unittest
Example tests
Now, paste into your Python shell the following test of Python's type-safety:
class MyTestCase(unittest.TestCase):
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
def test_2_cannot_add_int_and_str(self):
import operator
self.assertRaises(TypeError, operator.add, 1, '1')
Test one uses assertRaises
as a context manager, which ensures that the error is properly caught and cleaned up, while recorded.
We could also write it without the context manager, see test two. The first argument would be the error type you expect to raise, the second argument, the function you are testing, and the remaining args and keyword args will be passed to that function.
I think it's far more simple, readable, and maintainable to just to use the context manager.
Running the tests
To run the tests:
unittest.main(exit=False)
In Python 2.6, you'll probably need the following:
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
And your terminal should output the following:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
And we see that as we expect, attempting to add a 1
and a '1'
result in a TypeError
.
For more verbose output, try this:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Your code should follow this pattern (this is a unittest module style test):
def test_afunction_throws_exception(self):
try:
afunction()
except ExpectedException:
pass
except Exception as e:
self.fail('Unexpected exception raised:', e)
else:
self.fail('ExpectedException not raised')
On Python < 2.7 this construct is useful for checking for specific values in the expected exception. The unittest function assertRaises
only checks if an exception was raised.
from: http://www.lengrand.fr/2011/12/pythonunittest-assertraises-raises-error/
First, here is the corresponding (still dum :p) function in file dum_function.py :
def square_value(a):
"""
Returns the square value of a.
"""
try:
out = a*a
except TypeError:
raise TypeError("Input should be a string:")
return out
Here is the test to be performed (only this test is inserted):
import dum_function as df # import function module
import unittest
class Test(unittest.TestCase):
"""
The class inherits from unittest
"""
def setUp(self):
"""
This method is called before each test
"""
self.false_int = "A"
def tearDown(self):
"""
This method is called after each test
"""
pass
#---
## TESTS
def test_square_value(self):
# assertRaises(excClass, callableObj) prototype
self.assertRaises(TypeError, df.square_value(self.false_int))
if __name__ == "__main__":
unittest.main()
We are now ready to test our function! Here is what happens when trying to run the test :
======================================================================
ERROR: test_square_value (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_dum_function.py", line 22, in test_square_value
self.assertRaises(TypeError, df.square_value(self.false_int))
File "/home/jlengrand/Desktop/function.py", line 8, in square_value
raise TypeError("Input should be a string:")
TypeError: Input should be a string:
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
The TypeError is actullay raised, and generates a test failure. The problem is that this is exactly the behavior we wanted :s.
To avoid this error, simply run the function using lambda in the test call :
self.assertRaises(TypeError, lambda: df.square_value(self.false_int))
The final output :
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Perfect !
... and for me is perfect too!!
Thansk a lot Mr. Julien Lengrand-Lambert
You can build your own contextmanager
to check if the exception was raised.
import contextlib
@contextlib.contextmanager
def raises(exception):
try:
yield
except exception as e:
assert True
else:
assert False
And then you can use raises
like this:
with raises(Exception):
print "Hola" # Calls assert False
with raises(Exception):
raise Exception # Calls assert True
If you are using pytest
, this thing is implemented already. You can do pytest.raises(Exception)
:
Example:
def test_div_zero():
with pytest.raises(ZeroDivisionError):
1/0
And the result:
pigueiras@pigueiras$ py.test
================= test session starts =================
platform linux2 -- Python 2.6.6 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python
collected 1 items
tests/test_div_zero.py:6: test_div_zero PASSED
I use doctest[1] almost everywhere because I like the fact that I document and test my functions at the same time.
Have a look at this code:
def throw_up(something, gowrong=False):
"""
>>> throw_up('Fish n Chips')
Traceback (most recent call last):
...
Exception: Fish n Chips
>>> throw_up('Fish n Chips', gowrong=True)
'I feel fine!'
"""
if gowrong:
return "I feel fine!"
raise Exception(something)
if __name__ == '__main__':
import doctest
doctest.testmod()
If you put this example in a module and run it from the command line both test cases are evaluated and checked.
[1] Python documentation: 23.2 doctest -- Test interactive Python examples
Have a look at the assertRaises method of the unittest
module.
I just discovered that the Mock library provides an assertRaisesWithMessage() method (in its unittest.TestCase subclass), which will check not only that the expected exception is raised, but also that it is raised with the expected message:
from testcase import TestCase
import mymod
class MyTestCase(TestCase):
def test1(self):
self.assertRaisesWithMessage(SomeCoolException,
'expected message',
mymod.myfunc)
You can use assertRaises from the unittest module
import unittest
class TestClass():
def raises_exception(self):
raise Exception("test")
class MyTestCase(unittest.TestCase):
def test_if_method_raises_correct_exception(self):
test_class = TestClass()
# note that you dont use () when passing the method to assertRaises
self.assertRaises(Exception, test_class.raises_exception)
There are a lot of answers here. The code shows how we can create an Exception, how we can use that exception in our methods, and finally, how you can verify in a unittest, the correct exceptions being raised.
import unittest
class DeviceException(Exception):
def __init__(self, msg, code):
self.msg = msg
self.code = code
def __str__(self):
return repr("Error {}: {}".format(self.code, self.msg))
class MyDevice(object):
def __init__(self):
self.name = 'DefaultName'
def setParameter(self, param, value):
if isinstance(value, str):
setattr(self, param , value)
else:
raise DeviceException('Incorrect type of argument passed. Name expects a string', 100001)
def getParameter(self, param):
return getattr(self, param)
class TestMyDevice(unittest.TestCase):
def setUp(self):
self.dev1 = MyDevice()
def tearDown(self):
del self.dev1
def test_name(self):
""" Test for valid input for name parameter """
self.dev1.setParameter('name', 'MyDevice')
name = self.dev1.getParameter('name')
self.assertEqual(name, 'MyDevice')
def test_invalid_name(self):
""" Test to check if error is raised if invalid type of input is provided """
self.assertRaises(DeviceException, self.dev1.setParameter, 'name', 1234)
def test_exception_message(self):
""" Test to check if correct exception message and code is raised when incorrect value is passed """
with self.assertRaises(DeviceException) as cm:
self.dev1.setParameter('name', 1234)
self.assertEqual(cm.exception.msg, 'Incorrect type of argument passed. Name expects a string', 'mismatch in expected error message')
self.assertEqual(cm.exception.code, 100001, 'mismatch in expected error code')
if __name__ == '__main__':
unittest.main()