1190

How does one write a unit test that fails only if a function doesn't throw an expected exception?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Daryl Spitzer
  • 143,156
  • 76
  • 154
  • 173

19 Answers19

976

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)
Robert Siemer
  • 32,405
  • 11
  • 84
  • 94
Moe
  • 28,607
  • 10
  • 51
  • 67
  • 26
    is there a way to do the opposite of this? Like it fails only if the function does throw the exception? – BUInvent Jul 26 '17 at 19:44
  • 152
    Note that to pass arguments to `myfunc` you need to add them as arguments to the assertRaises call. See Daryl Spitzer's answer. – cbron Feb 21 '18 at 16:43
  • 2
    is there a way to allow for multiple exception types? – Dinesh Oct 30 '18 at 04:20
  • 1
    You can use Python's Built-in Exceptions to quickly test the assertion; using @Moe's answer above for example: `self.assertRaises(TypeError, mymod.myfunc)`. You can find a full list of the Built-in Exceptions here: https://docs.python.org/3/library/exceptions.html#bltin-exceptions – Wachaga Mwaura Feb 07 '19 at 17:51
  • 12
    Same thing for testing class constructors: `self.assertRaises(SomeCoolException, Constructor, arg1)` – tschumann Jun 08 '19 at 05:49
  • 6
    another way to pass arguments would be `self.assertRaises(MyException, lambda: do_something(with=some, arguments))` – jemand771 Mar 05 '20 at 08:06
  • 6
    To answer BUInvent's question, how to do the opposite: Just call `mymod.myfunc()` from your test. If it raises, that exception will fail the test. This is the default behavior for anything you call from a test. – Jonathan Hartley Jul 10 '20 at 21:32
  • I find it easier to use it as a context manager: `with self.assertRaises(SomeCoolException, 'possible message'): run_my_faulty_function()` – physicalattraction Jun 04 '21 at 11:41
  • @BUInvent, if you don't add anything, and just run the code, the test will fail if any kind of exception is thrown. – cowlinator Jun 16 '21 at 03:47
  • `self.assertRaises(AssertionError, empty_queue.pop())` doesn't work for me, is there a way to expect `AssertionError` to be thrown? – mercury0114 May 07 '22 at 08:38
667

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()

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))
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Art
  • 23,747
  • 29
  • 89
  • 101
  • 8
    I'm using Python 2.7.10, and the above doesn't work; `context.exception` does not give the message; it is a type. – LateCoder Feb 25 '16 at 16:55
  • 9
    Also in Python 2.7 (at least in my 2.7.6) using `import unittest2`, you need to use `str()`, i.e. `self.assertTrue('This is broken' in str(context.exception))`. – Sohail Si Jan 19 '17 at 01:00
  • 4
    Two things: 1. You can use assertIn instead of assertTrue. E.g. self.assertIn('This is broken', context.exception) 2. In my case, using 2.7.10, context.exception appears to be an array of characters. Using str doesn't work. I ended up doing this: ''.join(context.exception) So, put together: self.assertIn('This is broken', ''.join(context.exception)) – blockcipher Feb 07 '17 at 14:38
  • 1
    Is it normal that your method clogs up the test console with the exception's Traceback? How do I prevent that from happening? – MadPhysicist Mar 15 '17 at 14:10
  • I am using 2.7.12 now, and it works. It is true that context.exception is still a type, but it works, no need of str(context.exception). – zhihong May 11 '17 at 10:45
  • 1
    later I found another way to get the message as str of the exception, it is err = context.exception.message. And then can use also use self.assertEqual(err, 'This is broken') to do the test. – zhihong May 11 '17 at 11:04
  • Or, if you don't care about the exception message, you can simplify this to: `with self.assertRaises(Exception): broken_function()` – outofthecave May 19 '20 at 15:52
  • Try self.assertTrue('This is broken' in context.exception.value) instead – msashish Jul 08 '20 at 13:18
  • A gotcha here: Make sure the `assertTrue` is OUTSIDE of the context manager, otherwise the manager will eat the exception that results when your `assertTrue` fails and the test will pass erroneously. – John Dec 22 '22 at 19:46
512

The code in my previous answer can be simplified to:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction)

And if a function takes arguments, just pass them into assertRaises like this:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145
Daryl Spitzer
  • 143,156
  • 76
  • 154
  • 173
  • 73
    The second snipped about what to do when argument is passed was really helpful. – Sabyasachi Jun 19 '18 at 08:07
  • 2
    I'm using `2.7.15`. If `afunction` in `self.assertRaises(ExpectedException, afunction, arg1, arg2)` is the class initializer, you need to pass `self` as the first argument e.g., `self.assertRaises(ExpectedException, Class, self, arg1, arg2)` – Minh Tran Aug 24 '18 at 15:31
  • 1
    it also work if arg must be of type parameter=value, like: self.assertRaises(ExpectedException, afunction, arg1=val1) – Alvaro Rodriguez Scelza Jun 01 '21 at 17:13
167

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))
Community
  • 1
  • 1
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
67

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:
       self.fail('unexpected exception raised')
    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.

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
Daryl Spitzer
  • 143,156
  • 76
  • 154
  • 173
  • 3
    and method self.fail takes only one argument – mdob Oct 14 '12 at 21:54
  • 6
    This seems overly complicated for testing if a function throws an exception. Since any exception other than that exception will error the test and not throwing an exception will fail the test, it seems like the only difference is that if you get a different exception with `assertRaises` you will get an ERROR instead of a FAIL. – unflores Jan 21 '15 at 12:52
45

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 actually 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!!

Thanks a lot, Mr. Julien Lengrand-Lambert.


This test assert actually returns a false positive. That happens because the lambda inside the 'assertRaises' is the unit that raises type error and not the tested function.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
macm
  • 1,959
  • 23
  • 25
  • 17
    Just a note, you don't need the lambda. The line `self.assertRaises(TypeError, df.square_value(self.false_int))` calls the method and returns the result. What you want is to pass the method and any arguments and let the unittest to call it: `self.assertRaises(TypeError, df.square_value, self.false_int)` – Roman Kutlak Aug 08 '13 at 12:36
  • 1
    And in 2021, a friend of mine discovered that my blog was used to answer the question. Thanks for the kudos @macm! – jlengrand Mar 18 '21 at 10:05
42

As I haven't seen any detailed explanation on how to check if we got a specific exception among a list of accepted one using context manager, or other exception details I will add mine (checked on Python 3.8).

If I just want to check that function is raising for instance TypeError, I would write:

with self.assertRaises(TypeError):
    function_raising_some_exception(parameters)

If I want to check that function is raising either TypeError or IndexError, I would write:

with self.assertRaises((TypeError,IndexError)):
    function_raising_some_exception(parameters)

And if I want even more details about the Exception raised I could catch it in a context like this:

# Here I catch any exception
with self.assertRaises(Exception) as e:
    function_raising_some_exception(parameters)

# Here I check actual exception type (but I could
# check anything else about that specific exception,
# like it's actual message or values stored in the exception)
self.assertTrue(type(e.exception) in [TypeError,MatrixIsSingular])
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
kriss
  • 23,497
  • 17
  • 97
  • 116
  • 4
    This answer is exceptionally (ha!) useful because of the type check and suggestion for checking anything else. This helped me check the explicit error message with `self.assertEqual(e.exception.args[0], "Values cannot be a generator. Use list(generator) instead.",)`. – Steve Piercy Sep 03 '20 at 04:05
  • Context managed errors are the best. They allow you to test the error message too! – Florian Fasmeyer Nov 20 '20 at 10:13
33

If you are using pytest you can use pytest.raises(Exception):

Example:

def test_div_zero():
    with pytest.raises(ZeroDivisionError):
        1/0

And the result:

$ 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

Or 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
Pigueiras
  • 18,778
  • 10
  • 64
  • 87
18

If you are using Python 3, in order to assert an exception along with its message, you can use assertRaises in context manager and pass the message as a msg keyword argument like so:

import unittest

def your_function():
    raise RuntimeError('your exception message')

class YourTestCase(unittest.TestCase):
    def test(self):
        with self.assertRaises(RuntimeError, msg='your exception message'):
            your_function()


if __name__ == '__main__':
    unittest.main()
ifedapo olarewaju
  • 3,031
  • 1
  • 21
  • 25
11

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
pi.
  • 21,112
  • 8
  • 38
  • 59
  • 4
    I love doctest, but I find it supplements rather than replaces unittest. – TimothyAWiseman Oct 05 '12 at 21:17
  • 3
    Is doctest less likely to play nice with automated refactoring? I suppose a refactoring tool designed for python *should* be aware of docstrings. Can anyone comment from their experience? – kdbanman Jun 15 '15 at 16:37
7

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 unit test, 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()
mrsrinivas
  • 34,112
  • 13
  • 125
  • 125
Arindam Roychowdhury
  • 5,927
  • 5
  • 55
  • 63
  • 1
    the last test `test_exception_message` was really helpful. Needed to get the returned exception message. Thankyou – Anum Sheraz Oct 25 '21 at 22:29
6

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)
Daryl Spitzer
  • 143,156
  • 76
  • 154
  • 173
  • 2
    Unfortunately, it doesn't provide it anymore.. But the above answer of @Art (http://stackoverflow.com/a/3166985/1504046) gives the same result – Rmatt Jan 31 '13 at 14:23
5

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 don’t use () when passing the method to assertRaises
    self.assertRaises(Exception, test_class.raises_exception)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bruno Carvalho
  • 167
  • 3
  • 7
5

There are 4 options (you'll find full example in the end):

assertRaises with context manager

def test_raises(self):
    with self.assertRaises(RuntimeError):
        raise RuntimeError()

If you want to check the exception message (see the "assertRaisesRegex with context manager" option below to check only part of it):

def test_raises(self):
    with self.assertRaises(RuntimeError) as error:
        raise RuntimeError("your exception message")
    self.assertEqual(str(error.exception), "your exception message")

assertRaises one-liner

Pay attention: instead of function call, here you use your function as callable (without round brackets).

def test_raises(self):
    self.assertRaises(RuntimeError, your_function)

assertRaisesRegex with context manager

Second parameter is regex expression and is mandatory. Handy when you want check only part of the exception message.

def test_raises_regex(self):
    with self.assertRaisesRegex(RuntimeError, r'.* exception message'):
        raise RuntimeError('your exception message')

assertRaisesRegex one-liner

Second parameter is regex expression and is mandatory. Handy when you want check only part of the exception message.

Pay attention: instead of function call, here you use your function as callable (without round brackets).

def test_raises_regex(self):
    self.assertRaisesRegex(RuntimeError, r'.* exception message', your_function)

Full code example:

import unittest

def your_function():
    raise RuntimeError('your exception message')

class YourTestCase(unittest.TestCase):

    def test_1_raises_context_manager(self):
        with self.assertRaises(RuntimeError):
            your_function()

    def test_1b_raises_context_manager_and_error_message(self):
        with self.assertRaises(RuntimeError) as error:
            your_function()
        self.assertEqual(str(error.exception), "your exception message")

    def test_2_raises_oneliner(self):
        self.assertRaises(RuntimeError, your_function)

    def test_3_raises_regex_context_manager(self):
        with self.assertRaisesRegex(RuntimeError, r'.* exception message'):
            your_function()

    def test_4_raises_regex_oneliner(self):
        self.assertRaisesRegex(RuntimeError, r'.* exception message', your_function)

if __name__ == '__main__':
    unittest.main()

Although it's up to developer which style to follow I prefer both methods using context manager.

Paolo Rovelli
  • 9,396
  • 2
  • 58
  • 37
egvo
  • 1,493
  • 18
  • 26
4

For those on Django, you can use context manager to run the faulty function and assert it raises the exception with a certain message using assertRaisesMessage

with self.assertRaisesMessage(SomeException,'Some error message e.g 404 Not Found'):
    faulty_funtion()

Denis Biwott
  • 430
  • 5
  • 4
  • 3
    `assertRaisesMessage` is a Django only method and not a native Python Testcase class method as shown in the docs [here](https://docs.python.org/3/library/unittest.html) please edit your answer to clarify this. – Matthew Barlowe Dec 30 '20 at 21:45
2

For await/async aiounittest there is a slightly different pattern:

https://aiounittest.readthedocs.io/en/latest/asynctestcase.html#aiounittest.AsyncTestCase

async def test_await_async_fail(self):
    with self.assertRaises(Exception) as e:
        await async_one()
Stephan Schielke
  • 2,744
  • 7
  • 34
  • 40
2

This will raise TypeError if setting stock_id to an Integer in this class will throw the error, the test will pass if this happens and fails otherwise

def set_string(prop, value):
   if not isinstance(value, str):
      raise TypeError("i told you i take strings only ")
   return value

class BuyVolume(ndb.Model):
    stock_id = ndb.StringProperty(validator=set_string)

from pytest import raises
buy_volume_instance: BuyVolume = BuyVolume()
with raises(TypeError):
  buy_volume_instance.stock_id = 25
-1

Unit testing with unittest would be preferred, but if you would like a quick fix, we can catch the exception, assign it to a variable, and see if that variable is an instance of that exception class.

Lets assume our bad function throws a ValueError.

    try:
      bad_function()
    except ValueError as e:
      assert isinstance(e, ValueError)
-6

While all the answers are perfectly fine, I was looking for a way to test if a function raised an exception without relying on unit testing frameworks and having to write test classes.

I ended up writing the following:

def assert_error(e, x):
    try:
        e(x)
    except:
        return
    raise AssertionError()

def failing_function(x):
    raise ValueError()

def dummy_function(x):
    return x

if __name__=="__main__":
    assert_error(failing_function, 0)
    assert_error(dummy_function, 0)

And it fails on the right line:

Traceback (most recent call last):
  File "assert_error.py", line 16, in <module>
    assert_error(dummy_function, 0)
  File "assert_error.py", line 6, in assert_error
    raise AssertionError()
AssertionError
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
RUser4512
  • 1,050
  • 9
  • 23