Testing exceptions
==================

.. currentmodule:: testfixtures

The :mod:`unittest` support for asserting that exceptions are raised
when expected is fairly weak. Like many other Python testing
libraries, TestFixtures has tools to help with this depending on what
version of Python you are using.

The :class:`ShouldRaise` context manager
----------------------------------------

If you are using a version of Python where the :keyword:`with`
statement can be used, it's recommended that you use the
:class:`ShouldRaise` context manager. 

Suppose we wanted to test the following function to make sure that the
right exception was raised:

.. code-block:: python

  def the_thrower(throw=True):
      if throw:
          raise ValueError('Not good!')

The following example shows how to test that the correct exception is
raised:

>>> from testfixtures import ShouldRaise
>>> with ShouldRaise(ValueError('Not good!')):
...     the_thrower()

If the exception raise doesn't match the one expected,
:class:`ShouldRaise` will raise an :class:`AssertionError`
causing the tests in which it occurs to fail:

>>> with ShouldRaise(ValueError('Is good!')):
...     the_thrower()
Traceback (most recent call last):
...
AssertionError: ValueError('Not good!',) raised, ValueError('Is good!',) expected

If you're not concerned about anything more than the type of the
exception that's raised, you can check as follows:

>>> from testfixtures import ShouldRaise
>>> with ShouldRaise(ValueError):
...     the_thrower()

If you're feeling slack and just want to check that an exception is
raised, but don't care about the type of that exception, the following
will suffice:

>>> from testfixtures import ShouldRaise
>>> with ShouldRaise():
...     the_thrower()

If no exception is raised by the code under test, :class:`ShouldRaise`
will raise an :class:`AssertionError` to indicate this:

>>> from testfixtures import ShouldRaise
>>> with ShouldRaise():
...     the_thrower(throw=False)
Traceback (most recent call last):
...
AssertionError: No exception raised!

:class:`ShouldRaise` has been implemented such that it can be
successfully used to test if code raises both :class:`SystemExit` and
:class:`KeyboardInterrupt` exceptions.
 
The :func:`should_raise` wrapper function
-----------------------------------------

The basic idea of the :func:`should_raise` wrapper function is that it
returns an object that that can be used in place of the original
object where an action is expected to raise an exception. It's
slightly less clear to use than the :class:`ShouldRaise` context
manager and should only be used where the :keyword:`with` statement is
not available.

Suppose we wanted to test the following function to make sure that the
right exception was raised:

.. code-block:: python

  def the_thrower(a,b,throw=True):
      if throw:
          raise ValueError('%s != %s'%(a,b))

Testing is done by first creating a wrapper with the
:func:`should_raise` function: 

>>> from testfixtures import should_raise
>>> wrapped_thrower = should_raise(the_thrower,ValueError('1 != 2'))

The wrapper can now be called exactly as the original function, and
any exception raised will be compared against the expected one:

>>> wrapped_thrower(1,2)

If an incorrect exception is raised, an :class:`AssertionError` will
be raised:

>>> wrapped_thrower(1,1)
Traceback (most recent call last):
...
AssertionError: ValueError('1 != 1',) raised, ValueError('1 != 2',) expected

If no exception is raised by the code under test, an
:class:`AssertionError` indicating this will be raised:

>>> wrapped_thrower(1,2,throw=False)
Traceback (most recent call last):
...
AssertionError: None raised, ValueError('1 != 2',) expected

If the parameters of the exception are not important to the test, a
wrapper can be created which only checks for the type of the
exception:

>>> wrapped_thrower = should_raise(the_thrower,ValueError)
>>> wrapped_thrower(1,2)
>>> wrapped_thrower(1,1)

Furthermore, if even the type of the exception being raised is not
important, then it need not be specified:

>>> wrapped_thrower = should_raise(the_thrower)
>>> wrapped_thrower(1,2)

The wrappers returned can also be used to check for exceptions that
occur when key lookups are performed:

>>> class MyDict(dict):
...    pass
>>> wrapped_instance = should_raise(MyDict(),KeyError('foo'))
>>> wrapped_instance['foo']

These wrappers have also been implemented such that they can be
successfully used to test if code raises both :class:`SystemExit` and
:class:`KeyboardInterrupt` exceptions.
