diff --git a/README.rst b/README.rst
index aab5b820f005a51f1bdef722eb2a322fd534cd61..cb85261db4ae5b0e232341477bb68461d69f8a84 100644
--- a/README.rst
+++ b/README.rst
@@ -186,16 +186,16 @@ Check a specific call
 .. code-block:: python
 
     # Check the latest call...
-    print obj.function.last_call.args
-    print obj.function.last_call.kwargs
-    print obj.function.last_call.return_value
-    print obj.function.last_call.exception
+    print(obj.function.last_call.args)
+    print(obj.function.last_call.kwargs)
+    print(obj.function.last_call.return_value)
+    print(obj.function.last_call.exception)
 
     # For an older call...
-    print obj.function.calls[0].args
-    print obj.function.calls[0].kwargs
-    print obj.function.calls[0].return_value
-    print obj.function.calls[0].exception
+    print(obj.function.calls[0].args)
+    print(obj.function.calls[0].kwargs)
+    print(obj.function.calls[0].return_value)
+    print(obj.function.calls[0].exception)
 
 
 Also a good way of knowing whether it's even been called. ``last_call`` will
@@ -207,8 +207,14 @@ Check if the function was ever called
 
 .. code-block:: python
 
+    # Either one of these is fine.
+    self.assertSpyCalled(obj.function)
     self.assertTrue(obj.function.called)
 
+    # Or the inverse:
+    self.assertSpyNotCalled(obj.function)
+    self.assertFalse(obj.function.called)
+
 
 If the function was ever called at all, this will let you know.
 
@@ -219,12 +225,15 @@ Check if the function was ever called with certain arguments
 .. code-block:: python
 
     # Check if it was ever called with these arguments...
+    self.assertSpyCalledWith(obj.function, 'foo', bar='baz')
     self.assertTrue(obj.function.called_with('foo', bar='baz'))
 
     # Check a specific call...
+    self.assertSpyCalledWith(obj.function.calls[0], 'foo', bar='baz')
     self.assertTrue(obj.function.calls[0].called_with('foo', bar='baz'))
 
     # Check the last call...
+    self.assertSpyLastCalledWith(obj.function, 'foo', bar='baz')
     self.assertTrue(obj.function.last_called_with('foo', bar='baz'))
 
 
@@ -243,12 +252,15 @@ Check if the function ever returned a certain value
 .. code-block:: python
 
     # Check if the function ever returned a certain value...
+    self.assertSpyReturned(obj.function, 42)
     self.assertTrue(obj.function.returned(42))
 
     # Check a specific call...
+    self.assertSpyReturned(obj.function.calls[0], 42)
     self.assertTrue(obj.function.calls[0].returned(42))
 
     # Check the last call...
+    self.assertSpyLastReturned(obj.function, 42)
     self.assertTrue(obj.function.last_returned(42))
 
 
@@ -262,12 +274,15 @@ Check if a function ever raised a certain type of exception
 .. code-block:: python
 
     # Check if the function ever raised a certain exception...
+    self.assertSpyRaised(obj.function, TypeError)
     self.assertTrue(obj.function.raised(TypeError))
 
     # Check a specific call...
+    self.assertSpyRaised(obj.function.calls[0], TypeError)
     self.assertTrue(obj.function.calls[0].raised(TypeError))
 
     # Check the last call...
+    self.assertSpyLastRaised(obj.function, TypeError)
     self.assertTrue(obj.function.last_raised(TypeError))
 
 
@@ -276,16 +291,28 @@ You can also go a step further by checking the exception's message.
 .. code-block:: python
 
     # Check if the function ever raised an exception with a given message...
+    self.assertSpyRaisedWithMessage(
+        obj.function,
+        TypeError,
+        "'type' object is not iterable")
     self.assertTrue(obj.function.raised_with_message(
         TypeError,
         "'type' object is not iterable"))
 
     # Check a specific call...
+    self.assertSpyRaisedWithMessage(
+        obj.function.calls[0],
+        TypeError,
+        "'type' object is not iterable")
     self.assertTrue(obj.function.calls[0].raised_with_message(
         TypeError,
         "'type' object is not iterable"))
 
     # Check the last call...
+    self.assertSpyLastRaisedWithMessage(
+        obj.function,
+        TypeError,
+        "'type' object is not iterable")
     self.assertTrue(obj.function.last_raised_with_message(
         TypeError,
         "'type' object is not iterable"))
diff --git a/kgb/agency.py b/kgb/agency.py
index 8e1791054d960be120bf112f9e75f2fddb2ef1de..f7a166bddd085d820e5bbf6a25a1587dc5fc1606 100644
--- a/kgb/agency.py
+++ b/kgb/agency.py
@@ -2,7 +2,10 @@
 
 from __future__ import unicode_literals
 
-from kgb.spies import FunctionSpy
+from pprint import pformat
+
+from kgb.spies import FunctionSpy, SpyCall
+from unittest.util import safe_repr
 
 
 class SpyAgency(object):
@@ -101,3 +104,655 @@ class SpyAgency(object):
             spy.unspy(unregister=False)
 
         self.spies = []
+
+    def assertHasSpy(self, spy):
+        """Assert that a function has a spy.
+
+        This also accepts a spy as an argument, which will always return
+        ``True``.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The function or spy to check.
+
+        Raises:
+            AssertionError:
+                The function did not have a spy.
+        """
+        if not hasattr(spy, 'spy') and not isinstance(spy, FunctionSpy):
+            self.fail('%s has not been spied on.'
+                      % self._format_spy_or_call(spy))
+
+    def assertSpyCalled(self, spy):
+        """Assert that a function has been called at least once.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The function or spy to check.
+
+        Raises:
+            AssertionError:
+                The function was not called.
+        """
+        self.assertHasSpy(spy)
+
+        if not spy.called:
+            self.fail('%s was not called.' % self._format_spy_or_call(spy))
+
+    def assertSpyNotCalled(self, spy):
+        """Assert that a function has not been called.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The function or spy to check.
+
+        Raises:
+            AssertionError:
+                The function was called.
+        """
+        self.assertHasSpy(spy)
+
+        if spy.called:
+            call_count = len(spy.calls)
+
+            if call_count == 1:
+                msg = (
+                    '%s was called 1 time:'
+                    % self._format_spy_or_call(spy)
+                )
+            else:
+                msg = (
+                    '%s was called %d times:'
+                    % (self._format_spy_or_call(spy), call_count)
+                )
+
+            self.fail(
+                '%s\n'
+                '\n'
+                '%s'
+                % (
+                    msg,
+                    self._format_spy_calls(spy, self._format_spy_call_args),
+                ))
+
+    def assertSpyCallCount(self, spy, count):
+        """Assert that a function was called the given number of times.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The function or spy to check.
+
+            count (int):
+                The number of times the function is expected to have been
+                called.
+
+        Raises:
+            AssertionError:
+                The function was not called the specified number of times.
+        """
+        self.assertHasSpy(spy)
+
+        call_count = len(spy.calls)
+
+        if call_count != count:
+            if call_count == 1:
+                msg = '%s was called %d time, not %d.'
+            else:
+                msg = '%s was called %d times, not %d.'
+
+            self.fail(msg % (self._format_spy_or_call(spy), call_count,
+                             count))
+
+    def assertSpyCalledWith(self, spy_or_call, *expected_args,
+                            **expected_kwargs):
+        """Assert that a function was called with the given arguments.
+
+        If a spy is provided, all calls will be checked for a match.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy_or_call (callable or kgb.spies.FunctionSpy):
+                The function, spy, or call to check.
+
+            *expected_args (tuple):
+                Position arguments expected to be provided in any of the calls.
+
+            **expected_kwargs (dict):
+                Keyword arguments expected to be provided in any of the calls.
+
+        Raises:
+            AssertionError:
+                The function was not called with the provided arguments.
+        """
+        if isinstance(spy_or_call, FunctionSpy):
+            self.assertSpyCalled(spy_or_call)
+
+        if not spy_or_call.called_with(*expected_args, **expected_kwargs):
+            if isinstance(spy_or_call, SpyCall):
+                self.fail(
+                    'This call to %s was not passed args=%s, kwargs=%s.\n'
+                    '\n'
+                    'It was called with:\n'
+                    '\n'
+                    '%s'
+                    % (
+                        self._format_spy_or_call(spy_or_call),
+                        safe_repr(expected_args),
+                        safe_repr(expected_kwargs),
+                        self._format_spy_call_args(spy_or_call),
+                    ))
+            else:
+                self.fail(
+                    'No call to %s was passed args=%s, kwargs=%s.\n'
+                    '\n'
+                    'The following calls were recorded:\n'
+                    '\n'
+                    '%s'
+                    % (
+                        self._format_spy_or_call(spy_or_call),
+                        safe_repr(expected_args),
+                        safe_repr(expected_kwargs),
+                        self._format_spy_calls(
+                            spy_or_call,
+                            self._format_spy_call_args),
+                    ))
+
+    def assertSpyLastCalledWith(self, spy, *expected_args, **expected_kwargs):
+        """Assert that a function was last called with the given arguments.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The function or spy to check.
+
+            *expected_args (tuple):
+                Position arguments expected to be provided in the last call.
+
+            **expected_kwargs (dict):
+                Keyword arguments expected to be provided in the last call.
+
+        Raises:
+            AssertionError:
+                The function was not called last with the provided arguments.
+        """
+        self.assertSpyCalled(spy)
+
+        if not spy.last_called_with(*expected_args, **expected_kwargs):
+            self.fail(
+                'The last call to %s was not passed args=%s, kwargs=%s.\n'
+                '\n'
+                'It was last called with:\n'
+                '\n'
+                '%s'
+                % (
+                    self._format_spy_or_call(spy),
+                    safe_repr(expected_args),
+                    safe_repr(expected_kwargs),
+                    self._format_spy_call_args(spy.last_call),
+                ))
+
+    def assertSpyReturned(self, spy_or_call, return_value):
+        """Assert that a function call returned the given value.
+
+        If a spy is provided, all calls will be checked for a match.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy_or_call (callable or kgb.spies.FunctionSpy or
+                         kgb.spies.SpyCall):
+                The function, spy, or call to check.
+
+            return_value (object or type):
+                The value expected to be returned by any of the calls.
+
+        Raises:
+            AssertionError:
+                The function never returned the provided value.
+        """
+        if isinstance(spy_or_call, FunctionSpy):
+            self.assertSpyCalled(spy_or_call)
+
+        if not spy_or_call.returned(return_value):
+            if isinstance(spy_or_call, SpyCall):
+                self.fail(
+                    'This call to %s did not return %s.\n'
+                    '\n'
+                    'It returned:\n'
+                    '\n'
+                    '%s'
+                    % (
+                        self._format_spy_or_call(spy_or_call),
+                        safe_repr(return_value),
+                        self._format_spy_call_returned(spy_or_call),
+                    ))
+            else:
+                self.fail(
+                    'No call to %s returned %s.\n'
+                    '\n'
+                    'The following values have been returned:\n'
+                    '\n'
+                    '%s'
+                    % (
+                        self._format_spy_or_call(spy_or_call),
+                        safe_repr(return_value),
+                        self._format_spy_calls(
+                            spy_or_call,
+                            self._format_spy_call_returned),
+                    ))
+
+    def assertSpyLastReturned(self, spy, return_value):
+        """Assert that the last function call returned the given value.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The function or spy to check.
+
+            return_value (object or type):
+                The value expected to be returned by the last call.
+
+        Raises:
+            AssertionError:
+                The function's last call did not return the provided value.
+        """
+        self.assertSpyCalled(spy)
+
+        if not spy.last_returned(return_value):
+            self.fail(
+                'The last call to %s did not return %s.\n'
+                '\n'
+                'It last returned:\n'
+                '\n'
+                '%s'
+                % (
+                    self._format_spy_or_call(spy),
+                    safe_repr(return_value),
+                    self._format_spy_call_returned(spy.last_call),
+                ))
+
+    def assertSpyRaised(self, spy_or_call, exception_cls):
+        """Assert that a function call raised the given exception type.
+
+        If a spy is provided, all calls will be checked for a match.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy_or_call (callable or kgb.spies.FunctionSpy or
+                         kgb.spies.SpyCall):
+                The function, spy, or call to check.
+
+            exception_cls (type):
+                The exception type expected to be raised by one of the calls.
+
+        Raises:
+            AssertionError:
+                The function never raised the provided exception type.
+        """
+        if isinstance(spy_or_call, FunctionSpy):
+            self.assertSpyCalled(spy_or_call)
+
+        if not spy_or_call.raised(exception_cls):
+            if isinstance(spy_or_call, SpyCall):
+                if spy_or_call.exception is not None:
+                    self.fail(
+                        'This call to %s did not raise %s. It raised %s.'
+                        % (
+                            self._format_spy_or_call(spy_or_call),
+                            exception_cls.__name__,
+                            self._format_spy_call_raised(spy_or_call),
+                        ))
+                else:
+                    self.fail('This call to %s did not raise an exception.'
+                              % self._format_spy_or_call(spy_or_call))
+            else:
+                has_raised = any(
+                    call.exception is not None
+                    for call in spy_or_call.calls
+                )
+
+                if has_raised:
+                    self.fail(
+                        'No call to %s raised %s.\n'
+                        '\n'
+                        'The following exceptions have been raised:\n\n'
+                        '%s'
+                        % (
+                            self._format_spy_or_call(spy_or_call),
+                            exception_cls.__name__,
+                            self._format_spy_calls(
+                                spy_or_call,
+                                self._format_spy_call_raised),
+                        ))
+                else:
+                    self.fail('No call to %s raised an exception.'
+                              % self._format_spy_or_call(spy_or_call))
+
+    def assertSpyLastRaised(self, spy, exception_cls):
+        """Assert that the last function call raised the given exception type.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The function or spy to check.
+
+            exception_cls (type):
+                The exception type expected to be raised by the last call.
+
+        Raises:
+            AssertionError:
+                The last function call did not raise the provided exception
+                type.
+        """
+        self.assertSpyCalled(spy)
+
+        if not spy.last_raised(exception_cls):
+            if spy.last_call.exception is not None:
+                self.fail(
+                    'The last call to %s did not raise %s. It last '
+                    'raised %s.'
+                    % (
+                        self._format_spy_or_call(spy),
+                        exception_cls.__name__,
+                        self._format_spy_call_raised(spy.last_call),
+                    ))
+            else:
+                self.fail('The last call to %s did not raise an exception.'
+                          % self._format_spy_or_call(spy))
+
+    def assertSpyRaisedMessage(self, spy_or_call, exception_cls, message):
+        """Assert that a function call raised the given exception/message.
+
+        If a spy is provided, all calls will be checked for a match.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy_or_call (callable or kgb.spies.FunctionSpy or
+                         kgb.spies.SpyCall):
+                The function, spy, or call to check.
+
+            exception_cls (type):
+                The exception type expected to be raised by one of the calls.
+
+            message (bytes or unicode):
+                The expected message in a matching extension.
+
+        Raises:
+            AssertionError:
+                The function never raised the provided exception type with
+                the expected message.
+        """
+        if isinstance(spy_or_call, FunctionSpy):
+            self.assertSpyCalled(spy_or_call)
+
+        if not spy_or_call.raised_with_message(exception_cls, message):
+            if isinstance(spy_or_call, SpyCall):
+                if spy_or_call.exception is not None:
+                    self.fail(
+                        'This call to %s did not raise %s with message %r.\n'
+                        '\n'
+                        'It raised:\n'
+                        '\n'
+                        '%s'
+                        % (
+                            self._format_spy_or_call(spy_or_call),
+                            exception_cls.__name__,
+                            message,
+                            self._format_spy_call_raised_with_message(
+                                spy_or_call),
+                        ))
+                else:
+                    self.fail('This call to %s did not raise an exception.'
+                              % self._format_spy_or_call(spy_or_call))
+            else:
+                has_raised = any(
+                    call.exception is not None
+                    for call in spy_or_call.calls
+                )
+
+                if has_raised:
+                    self.fail(
+                        'No call to %s raised %s with message %r.\n'
+                        '\n'
+                        'The following exceptions have been raised:\n'
+                        '\n'
+                        '%s'
+                        % (
+                            self._format_spy_or_call(spy_or_call),
+                            exception_cls.__name__,
+                            message,
+                            self._format_spy_calls(
+                                spy_or_call,
+                                self._format_spy_call_raised_with_message),
+                        ))
+                else:
+                    self.fail('No call to %s raised an exception.'
+                              % self._format_spy_or_call(spy_or_call))
+
+    def assertSpyLastRaisedMessage(self, spy, exception_cls, message):
+        """Assert that the function last raised the given exception/message.
+
+        This will imply :py:meth:`assertHasSpy`.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The function or spy to check.
+
+            exception_cls (type):
+                The exception type expected to be raised by the last call.
+
+            message (bytes or unicode):
+                The expected message in the matching extension.
+
+        Raises:
+            AssertionError:
+                The last function call did not raise the provided exception
+                type with the expected message.
+        """
+        self.assertSpyCalled(spy)
+
+        if not spy.last_raised_with_message(exception_cls, message):
+            if spy.last_call.exception is not None:
+                self.fail(
+                    'The last call to %s did not raise %s with message %r.\n'
+                    '\n'
+                    'It last raised:\n'
+                    '\n'
+                    '%s'
+                    % (
+                        self._format_spy_or_call(spy),
+                        exception_cls.__name__,
+                        message,
+                        self._format_spy_call_raised_with_message(
+                            spy.last_call),
+                    ))
+            else:
+                self.fail('The last call to %s did not raise an exception.'
+                          % self._format_spy_or_call(spy))
+
+    def _format_spy_or_call(self, spy_or_call):
+        """Format a spy or call for output in an assertion message.
+
+        Args:
+            spy_or_call (callable or kgb.spies.FunctionSpy or
+                         kgb.spies.SpyCall):
+                The spy or call to format.
+
+        Returns:
+            unicode:
+            The formatted name of the function.
+        """
+        if isinstance(spy_or_call, FunctionSpy):
+            spy = spy_or_call.orig_func
+        elif isinstance(spy_or_call, SpyCall):
+            spy = spy_or_call.spy.orig_func
+        else:
+            spy = spy_or_call
+
+        name = spy.__name__
+
+        if isinstance(name, bytes):
+            name = name.decode('utf-8')
+
+        return name
+
+    def _format_spy_calls(self, spy, formatter):
+        """Format a list of calls for a spy.
+
+        Args:
+            spy (callable or kgb.spies.FunctionSpy):
+                The spy to format.
+
+            formatter (callable):
+                A formatting function used for each recorded call.
+
+        Returns:
+            unicode:
+            The formatted output of the calls.
+        """
+        return '\n\n'.join(
+            'Call %d:\n%s' % (i, formatter(call, indent=2))
+            for i, call in enumerate(spy.calls)
+        )
+
+    def _format_spy_call_args(self, call, indent=0):
+        """Format a call's arguments.
+
+        Args:
+            call (kgb.spies.SpyCall):
+                The call containing arguments to format.
+
+            indent (int, optional):
+                The indentation level for any output.
+
+        Returns:
+            unicode:
+            The formatted output of the arguments for the call.
+        """
+        return '%s\n%s' % (
+            self._format_spy_lines(call.args,
+                                   prefix='args=',
+                                   indent=indent),
+            self._format_spy_lines(call.kwargs,
+                                   prefix='kwargs=',
+                                   indent=indent),
+        )
+
+    def _format_spy_call_returned(self, call, indent=0):
+        """Format the return value from a call.
+
+        Args:
+            call (kgb.spies.SpyCall):
+                The call containing a return value to format.
+
+            indent (int, optional):
+                The indentation level for any output.
+
+        Returns:
+            unicode:
+            The formatted return value from the call.
+        """
+        return self._format_spy_lines(call.return_value,
+                                      indent=indent)
+
+    def _format_spy_call_raised(self, call, indent=0):
+        """Format the exception type raised by a call.
+
+        Args:
+            call (kgb.spies.SpyCall):
+                The call that raised an exception to format.
+
+            indent (int, optional):
+                The indentation level for any output.
+
+        Returns:
+            unicode:
+            The formatted name of the exception raised by a call.
+        """
+        return self._format_spy_lines(call.exception.__class__.__name__,
+                                      indent=indent,
+                                      format_data=False)
+
+    def _format_spy_call_raised_with_message(self, call, indent=0):
+        """Format the exception type and message raised by a call.
+
+        Args:
+            call (kgb.spies.SpyCall):
+                The call that raised an exception to format.
+
+            indent (int, optional):
+                The indentation level for any output.
+
+        Returns:
+            unicode:
+            The formatted name of the exception and accompanying message raised
+            by a call.
+        """
+        return '%s\n%s' % (
+            self._format_spy_lines(call.exception.__class__.__name__,
+                                   prefix='exception=',
+                                   indent=indent,
+                                   format_data=False),
+            self._format_spy_lines(str(call.exception),
+                                   prefix='message=',
+                                   indent=indent),
+        )
+
+    def _format_spy_lines(self, data, prefix='', indent=0, format_data=True):
+        """Format a multi-line list of output for an assertion message.
+
+        Unless otherwise specified, the provided data will be formatted
+        using :py:func:`pprint.pformat`.
+
+        The first line of data will be prefixed, if a prefix is provided.
+        Subsequent lines be aligned with the contents after the prefix.
+
+        All line will be indented by the given amount.
+
+        Args:
+            data (object):
+                The data to format.
+
+            prefix (unicode, optional):
+                An optional prefix for the first line in the data.
+
+            indent (int, optional):
+                The indentation level for any output.
+
+            format_data (bool, optional):
+                Whether to format the provided ``data`` using
+                :py:func:`pprint.pformat`.
+
+        Returns:
+            unicode:
+            The formatted string for the data.
+        """
+        indent_str = ' ' * indent
+
+        if format_data:
+            data = pformat(data)
+
+        data_lines = data.splitlines()
+        lines = ['%s%s%s' % (indent_str, prefix, data_lines[0])]
+
+        if len(data_lines) > 1:
+            indent_str = ' ' * (indent + len(prefix))
+
+            lines += [
+                '%s%s' % (indent_str, line)
+                for line in data_lines[1:]
+            ]
+
+        return '\n'.join(lines)
diff --git a/kgb/tests/test_spy_agency.py b/kgb/tests/test_spy_agency.py
index 956f6293cc97ae3cd1a640814011ff897a4150ac..f6fded3ac036597078318e92198ecad585cd46bf 100644
--- a/kgb/tests/test_spy_agency.py
+++ b/kgb/tests/test_spy_agency.py
@@ -1,6 +1,8 @@
+"""Unit tests for kgb.agency.SpyAgency."""
+
 from __future__ import unicode_literals
 
-import unittest
+from contextlib import contextmanager
 
 from kgb.agency import SpyAgency
 from kgb.tests.base import MathClass, TestCase
@@ -52,7 +54,9 @@ class SpyAgencyTests(TestCase):
         self.assertFalse(hasattr(MathClass.class_do_math, 'spy'))
 
 
-class MixinTests(SpyAgency, unittest.TestCase):
+class TestCaseMixinTests(SpyAgency, TestCase):
+    """Unit tests for SpyAgency as a TestCase mixin."""
+
     def test_spy_on(self):
         """Testing SpyAgency mixed in with spy_on"""
         obj = MathClass()
@@ -78,3 +82,707 @@ class MixinTests(SpyAgency, unittest.TestCase):
         self.assertEqual(obj.do_math, orig_do_math)
         self.assertFalse(hasattr(obj.do_math, 'spy'))
         self.assertEqual(func_dict, obj.do_math.__dict__)
+
+    def test_assertHasSpy_with_spy(self):
+        """Testing SpyAgency.assertHasSpy with spy"""
+        self.spy_on(MathClass.do_math,
+                    owner=MathClass)
+
+        # These should not fail.
+        self.assertHasSpy(MathClass.do_math)
+        self.assertHasSpy(MathClass.do_math.spy)
+
+    def test_assertHasSpy_without_spy(self):
+        """Testing SpyAgency.assertHasSpy without spy"""
+        with self._check_assertion('do_math has not been spied on.'):
+            self.assertHasSpy(MathClass.do_math)
+
+    def test_assertSpyCalled_with_called(self):
+        """Testing SpyAgency.assertSpyCalled with spy called"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math()
+
+        # These should not fail.
+        self.assertSpyCalled(obj.do_math)
+        self.assertSpyCalled(obj.do_math.spy)
+
+    def test_assertSpyCalled_without_called(self):
+        """Testing SpyAgency.assertSpyCalled without spy called"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        msg = 'do_math was not called.'
+
+        with self._check_assertion(msg):
+            self.assertSpyCalled(obj.do_math)
+
+        with self._check_assertion(msg):
+            self.assertSpyCalled(obj.do_math.spy)
+
+    def test_assertSpyNotCalled_without_called(self):
+        """Testing SpyAgency.assertSpyNotCalled without spy called"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        # These should not fail.
+        self.assertSpyNotCalled(obj.do_math)
+        self.assertSpyNotCalled(obj.do_math.spy)
+
+    def test_assertSpyNotCalled_with_called(self):
+        """Testing SpyAgency.assertSpyNotCalled with spy called"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(3, b=4)
+        obj.do_math(2, b=9)
+
+        msg = (
+            "do_math was called 2 times:\n"
+            "\n"
+            "Call 0:\n"
+            "  args=()\n"
+            "  kwargs={'a': 3, 'b': 4}\n"
+            "\n"
+            "Call 1:\n"
+            "  args=()\n"
+            "  kwargs={'a': 2, 'b': 9}"
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyNotCalled(obj.do_math)
+
+        with self._check_assertion(msg):
+            self.assertSpyNotCalled(obj.do_math.spy)
+
+
+    def test_assertSpyCallCount_with_expected_count(self):
+        """Testing SpyAgency.assertSpyCallCount with expected call count"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math()
+        obj.do_math()
+
+        # These should not fail.
+        self.assertSpyCallCount(obj.do_math, 2)
+        self.assertSpyCallCount(obj.do_math.spy, 2)
+
+    def test_assertSpyCallCount_without_expected_count(self):
+        """Testing SpyAgency.assertSpyCallCount without expected call count"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math()
+
+        with self._check_assertion('do_math was called 1 time, not 2.'):
+            self.assertSpyCallCount(obj.do_math, 2)
+
+        # Let's bump and test a plural result.
+        obj.do_math()
+
+        with self._check_assertion('do_math was called 2 times, not 3.'):
+            self.assertSpyCallCount(obj.do_math.spy, 3)
+
+    def test_assertSpyCalledWith_with_expected_arguments(self):
+        """Testing SpyAgency.assertSpyCalledWith with expected arguments"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1, b=4)
+        obj.do_math(2, b=9)
+
+        # These should not fail.
+        self.assertSpyCalledWith(obj.do_math, a=1, b=4)
+        self.assertSpyCalledWith(obj.do_math.calls[0], a=1, b=4)
+        self.assertSpyCalledWith(obj.do_math.spy, a=2, b=9)
+        self.assertSpyCalledWith(obj.do_math.spy.calls[1], a=2, b=9)
+
+    def test_assertSpyCalledWith_without_expected_arguments(self):
+        """Testing SpyAgency.assertSpyCalledWith without expected arguments"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1, b=4)
+        obj.do_math(2, b=9)
+
+        msg = (
+            "No call to do_math was passed args=(), kwargs={'x': 4, 'z': 1}.\n"
+            "\n"
+            "The following calls were recorded:\n"
+            "\n"
+            "Call 0:\n"
+            "  args=()\n"
+            "  kwargs={'a': 1, 'b': 4}\n"
+            "\n"
+            "Call 1:\n"
+            "  args=()\n"
+            "  kwargs={'a': 2, 'b': 9}"
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyCalledWith(obj.do_math, x=4, z=1)
+
+        with self._check_assertion(msg):
+            self.assertSpyCalledWith(obj.do_math.spy, x=4, z=1)
+
+        msg = (
+            "This call to do_math was not passed args=(),"
+            " kwargs={'x': 4, 'z': 1}.\n"
+            "\n"
+            "It was called with:\n"
+            "\n"
+            "args=()\n"
+            "kwargs={'a': 1, 'b': 4}"
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyCalledWith(obj.do_math.spy.calls[0], x=4, z=1)
+
+    def test_assertSpyLastCalledWith_with_expected_arguments(self):
+        """Testing SpyAgency.assertSpyLastCalledWith with expected arguments"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1, b=4)
+        obj.do_math(2, b=9)
+
+        # These should not fail.
+        self.assertSpyLastCalledWith(obj.do_math, a=2, b=9)
+        self.assertSpyLastCalledWith(obj.do_math.spy, a=2, b=9)
+
+    def test_assertSpyLastCalledWith_without_expected_arguments(self):
+        """Testing SpyAgency.assertSpyLastCalledWith without expected
+        arguments
+        """
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1, b=4)
+        obj.do_math(2, b=9)
+
+        msg = (
+            "The last call to do_math was not passed args=(),"
+            " kwargs={'a': 1, 'b': 4}.\n"
+            "\n"
+            "It was last called with:\n"
+            "\n"
+            "args=()\n"
+            "kwargs={'a': 2, 'b': 9}"
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyLastCalledWith(obj.do_math, a=1, b=4)
+
+        with self._check_assertion(msg):
+            self.assertSpyLastCalledWith(obj.do_math.spy, a=1, b=4)
+
+    def test_assertSpyReturned_with_expected_return(self):
+        """Testing SpyAgency.assertSpyReturned with expected return value"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1, b=4)
+        obj.do_math(2, b=9)
+
+        # These should not fail.
+        self.assertSpyReturned(obj.do_math, 5)
+        self.assertSpyReturned(obj.do_math.calls[0], 5)
+        self.assertSpyReturned(obj.do_math.spy, 11)
+        self.assertSpyReturned(obj.do_math.spy.calls[1], 11)
+
+    def test_assertSpyReturned_without_expected_return(self):
+        """Testing SpyAgency.assertSpyReturned without expected return value"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1, b=4)
+        obj.do_math(2, b=9)
+
+        msg = (
+            'No call to do_math returned 100.\n'
+            '\n'
+            'The following values have been returned:\n'
+            '\n'
+            'Call 0:\n'
+            '  5\n'
+            '\n'
+            'Call 1:\n'
+            '  11'
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyReturned(obj.do_math, 100)
+
+        with self._check_assertion(msg):
+            self.assertSpyReturned(obj.do_math.spy, 100)
+
+        msg = (
+            'This call to do_math did not return 100.\n'
+            '\n'
+            'It returned:\n'
+            '\n'
+            '5'
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyReturned(obj.do_math.calls[0], 100)
+
+    def test_assertSpyLastReturned_with_expected_return(self):
+        """Testing SpyAgency.assertSpyLastReturned with expected return value
+        """
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1, b=4)
+        obj.do_math(2, b=9)
+
+        # These should not fail.
+        self.assertSpyLastReturned(obj.do_math, 11)
+        self.assertSpyLastReturned(obj.do_math.spy, 11)
+
+    def test_assertSpyLastReturned_without_expected_return(self):
+        """Testing SpyAgency.assertSpyLastReturned without expected return
+        value
+        """
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1, b=4)
+        obj.do_math(2, b=9)
+
+        msg = (
+            'The last call to do_math did not return 5.\n'
+            '\n'
+            'It last returned:\n'
+            '\n'
+            '11'
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyLastReturned(obj.do_math, 5)
+
+        with self._check_assertion(msg):
+            self.assertSpyLastReturned(obj.do_math.spy, 5)
+
+    def test_assertSpyRaised_with_expected_exception(self):
+        """Testing SpyAgency.assertSpyRaised with expected exception raised"""
+        def _do_math(_self, a, *args, **kwargs):
+            if a == 1:
+                raise KeyError
+            elif a == 2:
+                raise ValueError
+
+        obj = MathClass()
+        self.spy_on(obj.do_math, call_fake=_do_math)
+
+        try:
+            obj.do_math(1)
+        except KeyError:
+            pass
+
+        try:
+            obj.do_math(2)
+        except ValueError:
+            pass
+
+        # These should not fail.
+        self.assertSpyRaised(obj.do_math, KeyError)
+        self.assertSpyRaised(obj.do_math.calls[0], KeyError)
+        self.assertSpyRaised(obj.do_math.spy, ValueError)
+        self.assertSpyRaised(obj.do_math.spy.calls[1], ValueError)
+
+    def test_assertSpyRaised_with_expected_no_exception(self):
+        """Testing SpyAgency.assertSpyRaised with expected completions without
+        exceptions
+        """
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1)
+        obj.do_math(2)
+
+        # These should not fail.
+        self.assertSpyRaised(obj.do_math, None)
+        self.assertSpyRaised(obj.do_math.calls[0], None)
+        self.assertSpyRaised(obj.do_math.spy, None)
+        self.assertSpyRaised(obj.do_math.spy.calls[1], None)
+
+    def test_assertSpyRaised_without_expected_exception(self):
+        """Testing SpyAgency.assertSpyRaised without expected exception raised
+        """
+        def _do_math(_self, a, *args, **kwargs):
+            if a == 1:
+                raise KeyError
+            elif a == 2:
+                raise ValueError
+
+        obj = MathClass()
+        self.spy_on(obj.do_math, call_fake=_do_math)
+
+        # First test without any exceptions raised
+        try:
+            obj.do_math(1)
+        except KeyError:
+            pass
+
+        try:
+            obj.do_math(2)
+        except ValueError:
+            pass
+
+        msg = (
+            'No call to do_math raised AttributeError.\n'
+            '\n'
+            'The following exceptions have been raised:\n'
+            '\n'
+            'Call 0:\n'
+            '  KeyError\n'
+            '\n'
+            'Call 1:\n'
+            '  ValueError'
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyRaised(obj.do_math, AttributeError)
+
+        with self._check_assertion(msg):
+            self.assertSpyRaised(obj.do_math.spy, AttributeError)
+
+        msg = (
+            'This call to do_math did not raise AttributeError. It raised '
+            'KeyError.'
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyRaised(obj.do_math.calls[0], AttributeError)
+
+    def test_assertSpyRaised_without_raised(self):
+        """Testing SpyAgency.assertSpyRaised without any exceptions raised"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        # First test without any exceptions raised
+        obj.do_math(1)
+        obj.do_math(2)
+
+        msg = 'No call to do_math raised an exception.'
+
+        with self._check_assertion(msg):
+            self.assertSpyRaised(obj.do_math, AttributeError)
+
+        with self._check_assertion(msg):
+            self.assertSpyRaised(obj.do_math.spy, AttributeError)
+
+        msg = 'This call to do_math did not raise an exception.'
+
+        with self._check_assertion(msg):
+            self.assertSpyRaised(obj.do_math.spy.calls[0], AttributeError)
+
+    def test_assertSpyLastRaised_with_expected_exception(self):
+        """Testing SpyAgency.assertSpyLastRaised with expected exception
+        raised
+        """
+        def _do_math(_self, a, *args, **kwargs):
+            if a == 1:
+                raise KeyError
+            elif a == 2:
+                raise ValueError
+
+        obj = MathClass()
+        self.spy_on(obj.do_math, call_fake=_do_math)
+
+        try:
+            obj.do_math(1)
+        except KeyError:
+            pass
+
+        try:
+            obj.do_math(2)
+        except ValueError:
+            pass
+
+        # These should not fail.
+        self.assertSpyLastRaised(obj.do_math, ValueError)
+        self.assertSpyLastRaised(obj.do_math.spy, ValueError)
+
+    def test_assertSpyLastRaised_with_expected_no_exception(self):
+        """Testing SpyAgency.assertSpyLastRaised with expected completion
+        without raising
+        """
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1)
+        obj.do_math(2)
+
+        # These should not fail.
+        self.assertSpyLastRaised(obj.do_math, None)
+        self.assertSpyLastRaised(obj.do_math.spy, None)
+
+    def test_assertSpyLastRaised_without_expected_exception(self):
+        """Testing SpyAgency.assertSpyLastRaised without expected exception
+        raised
+        """
+        def _do_math(_self, a, *args, **kwargs):
+            if a == 1:
+                raise KeyError
+            elif a == 2:
+                raise ValueError
+
+        obj = MathClass()
+        self.spy_on(obj.do_math, call_fake=_do_math)
+
+        try:
+            obj.do_math(1)
+        except KeyError:
+            pass
+
+        try:
+            obj.do_math(2)
+        except ValueError:
+            pass
+
+        msg = (
+            'The last call to do_math did not raise KeyError. It last '
+            'raised ValueError.'
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyLastRaised(obj.do_math, KeyError)
+
+        with self._check_assertion(msg):
+            self.assertSpyLastRaised(obj.do_math.spy, KeyError)
+
+    def test_assertSpyLastRaised_without_raised(self):
+        """Testing SpyAgency.assertSpyLastRaised without exception raised"""
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1)
+        obj.do_math(2)
+
+        msg = 'The last call to do_math did not raise an exception.'
+
+        with self._check_assertion(msg):
+            self.assertSpyLastRaised(obj.do_math, KeyError)
+
+        with self._check_assertion(msg):
+            self.assertSpyLastRaised(obj.do_math.spy, KeyError)
+
+    def test_assertSpyRaisedMessage_with_expected(self):
+        """Testing SpyAgency.assertSpyRaised with expected exception and
+        message raised
+        """
+        def _do_math(_self, a, *args, **kwargs):
+            if a == 1:
+                raise AttributeError('Bad key!')
+            elif a == 2:
+                raise ValueError('Bad value!')
+
+        obj = MathClass()
+        self.spy_on(obj.do_math, call_fake=_do_math)
+
+        try:
+            obj.do_math(1)
+        except AttributeError:
+            pass
+
+        try:
+            obj.do_math(2)
+        except ValueError:
+            pass
+
+        # These should not fail.
+        self.assertSpyRaisedMessage(obj.do_math, AttributeError, 'Bad key!')
+        self.assertSpyRaisedMessage(obj.do_math.calls[0], AttributeError,
+                                    'Bad key!')
+        self.assertSpyRaisedMessage(obj.do_math.spy, ValueError, 'Bad value!')
+        self.assertSpyRaisedMessage(obj.do_math.spy.calls[1], ValueError,
+                                    'Bad value!')
+
+    def test_assertSpyRaisedMessage_without_expected(self):
+        """Testing SpyAgency.assertSpyRaisedMessage without expected exception
+        and message raised
+        """
+        def _do_math(_self, a, *args, **kwargs):
+            if a == 1:
+                raise AttributeError('Bad key!')
+            elif a == 2:
+                raise ValueError('Bad value!')
+
+        obj = MathClass()
+        self.spy_on(obj.do_math, call_fake=_do_math)
+
+        try:
+            obj.do_math(1)
+        except AttributeError:
+            pass
+
+        try:
+            obj.do_math(2)
+        except ValueError:
+            pass
+
+        # Note that we may end up with different string types with different
+        # prefixes on different versions of Python, so we need to repr these.
+        msg = (
+            'No call to do_math raised AttributeError with message %r.\n'
+            '\n'
+            'The following exceptions have been raised:\n'
+            '\n'
+            'Call 0:\n'
+            '  exception=AttributeError\n'
+            '  message=%r\n'
+            '\n'
+            'Call 1:\n'
+            '  exception=ValueError\n'
+            '  message=%r'
+            % ('Bad key...', str('Bad key!'), str('Bad value!'))
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyRaisedMessage(obj.do_math, AttributeError,
+                                        'Bad key...')
+
+        with self._check_assertion(msg):
+            self.assertSpyRaisedMessage(obj.do_math.spy, AttributeError,
+                                        'Bad key...')
+
+        msg = (
+            'This call to do_math did not raise AttributeError with message'
+            ' %r.\n'
+            '\n'
+            'It raised:\n'
+            '\n'
+            'exception=AttributeError\n'
+            'message=%r'
+            % ('Bad key...', str('Bad key!'))
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyRaisedMessage(obj.do_math.calls[0], AttributeError,
+                                        'Bad key...')
+
+    def test_assertSpyRaisedMessage_without_raised(self):
+        """Testing SpyAgency.assertSpyRaisedMessage without exception raised
+        """
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1)
+        obj.do_math(2)
+
+        msg = 'No call to do_math raised an exception.'
+
+        with self._check_assertion(msg):
+            self.assertSpyRaisedMessage(obj.do_math, KeyError, '...')
+
+        with self._check_assertion(msg):
+            self.assertSpyRaisedMessage(obj.do_math.spy, KeyError, '...')
+
+    def test_assertSpyLastRaisedMessage_with_expected(self):
+        """Testing SpyAgency.assertSpyLastRaised with expected exception and
+        message raised
+        """
+        def _do_math(_self, a, *args, **kwargs):
+            if a == 1:
+                raise AttributeError('Bad key!')
+            elif a == 2:
+                raise ValueError('Bad value!')
+
+        obj = MathClass()
+        self.spy_on(obj.do_math, call_fake=_do_math)
+
+        try:
+            obj.do_math(1)
+        except AttributeError:
+            pass
+
+        try:
+            obj.do_math(2)
+        except ValueError:
+            pass
+
+        # These should not fail.
+        self.assertSpyLastRaisedMessage(obj.do_math, ValueError, 'Bad value!')
+        self.assertSpyLastRaisedMessage(obj.do_math.spy, ValueError,
+                                        'Bad value!')
+
+    def test_assertSpyLastRaisedMessage_without_expected(self):
+        """Testing SpyAgency.assertSpyLastRaisedMessage without expected exception
+        and message raised
+        """
+        def _do_math(_self, a, *args, **kwargs):
+            if a == 1:
+                raise AttributeError('Bad key!')
+            elif a == 2:
+                raise ValueError('Bad value!')
+
+        obj = MathClass()
+        self.spy_on(obj.do_math, call_fake=_do_math)
+
+        try:
+            obj.do_math(1)
+        except AttributeError:
+            pass
+
+        try:
+            obj.do_math(2)
+        except ValueError:
+            pass
+
+        # Note that we may end up with different string types with different
+        # prefixes on different versions of Python, so we need to repr these.
+        msg = (
+            'The last call to do_math did not raise AttributeError with '
+            'message %r.\n'
+            '\n'
+            'It last raised:\n'
+            '\n'
+            'exception=ValueError\n'
+            'message=%r'
+            % ('Bad key!', str('Bad value!'))
+        )
+
+        with self._check_assertion(msg):
+            self.assertSpyLastRaisedMessage(obj.do_math, AttributeError,
+                                            'Bad key!')
+
+        with self._check_assertion(msg):
+            self.assertSpyLastRaisedMessage(obj.do_math.spy, AttributeError,
+                                            'Bad key!')
+
+    def test_assertSpyLastRaisedMessage_without_raised(self):
+        """Testing SpyAgency.assertSpyLastRaisedMessage without exception raised
+        """
+        obj = MathClass()
+        self.spy_on(obj.do_math)
+
+        obj.do_math(1)
+        obj.do_math(2)
+
+        msg = 'The last call to do_math did not raise an exception.'
+
+        with self._check_assertion(msg):
+            self.assertSpyLastRaisedMessage(obj.do_math, KeyError, '...')
+
+        with self._check_assertion(msg):
+            self.assertSpyLastRaisedMessage(obj.do_math.spy, KeyError, '...')
+
+    @contextmanager
+    def _check_assertion(self, msg):
+        """Check that the expected assertion and message is raised.
+
+        Args:
+            msg (unicode):
+                The assertion message.
+
+        Context:
+            The context used to run an assertion.
+        """
+        with self.assertRaises(AssertionError) as ctx:
+            yield
+
+        self.assertEqual(str(ctx.exception), msg)
