diff --git a/README.rst b/README.rst
index a90c0ff63b8c14999bd35a22f17378e891b28dc1..e2e149258f99574553a3eb2fd6a48a7939deab80 100644
--- a/README.rst
+++ b/README.rst
@@ -434,7 +434,14 @@ operation.
        },
        {
            'args': ('trap_tile',),
-           'call_fake': _spill_hot_oil,
+           'op': SpyOpMatchInOrder([
+               {
+                   'call_fake': _spill_hot_oil,
+               },
+               {
+                   'call_fake': _drop_torch,
+               },
+           ]),
        },
        {
            'args': ('infrared_camera',),
@@ -466,6 +473,11 @@ using ``SpyOpMatchInOrder``.
            'call_fake': _start_countdown,
        },
        {
+           'args': (42, 42, 42, 42, 42, 42),
+           'op': kgb.SpyOpRaise(Kaboom()),
+           'call_original': True,
+       },
+       {
            'args': (4, 8, 15, 16, 23, 42),
            'kwargs': {
                'secret_button_pushed': True,
diff --git a/kgb/ops.py b/kgb/ops.py
index d81a144ebf41e07df8b504c25607618538587261..94dae9e8eecff1784e71b19a84bde9f137a8e0d8 100644
--- a/kgb/ops.py
+++ b/kgb/ops.py
@@ -41,7 +41,7 @@ class BaseSpyOperation(object):
         """
         raise NotImplementedError
 
-    def setup(self, spy):
+    def setup(self, spy, force_unbound=False):
         """Set up the operation.
 
         This associates the spy with the operation, and then returns a fake
@@ -52,13 +52,21 @@ class BaseSpyOperation(object):
             spy (kgb.spies.FunctionSpy):
                 The spy this operation is for.
 
+            force_unbound (bool, optional):
+                Whether to force building an unbound fake function. This is
+                needed for any functions called by an operation itself (and
+                is thus used for nested operations).
+
+                Version Added:
+                    6.1
+
         Returns:
             callable:
             The fake function to set up with the spy.
         """
         self.spy = spy
 
-        if spy.func_type == spy.TYPE_BOUND_METHOD:
+        if spy.func_type == spy.TYPE_BOUND_METHOD and not force_unbound:
             def fake_func(_self, *args, **kwargs):
                 return self._on_spy_call(*args, **kwargs)
         else:
@@ -131,8 +139,16 @@ class BaseMatchingSpyOperation(BaseSpyOperation):
             Whether to call the original function. This is the default if
             ``call_fake`` is not provided.
 
+        ``op`` (:py:class:`BaseSpyOperation`, optional):
+            A spy operation to call instead of ``call_fake`` or
+            ``call_original``.
+
         Subclasses may define custom keys.
 
+        Version Changed:
+            6.1:
+            Added support for ``op``.
+
         Args:
             calls (list of dict):
                 A list of call match configurations.
@@ -141,6 +157,48 @@ class BaseMatchingSpyOperation(BaseSpyOperation):
 
         self._calls = calls
 
+    def setup(self, spy, **kwargs):
+        """Set up the operation.
+
+        This invokes the common behavior for setting up a spy operation, and
+        then goes through all registered calls to see if any call-provided
+        operations also need to be set up.
+
+        Version Added:
+            6.1
+
+        Args:
+            spy (kgb.spies.FunctionSpy):
+                The spy this operation is for.
+
+            **kwargs (dict):
+                Additional keyword arguments to pass to the parent.
+
+        Returns:
+            callable:
+            The fake function to set up with the spy.
+        """
+        result = super(BaseMatchingSpyOperation, self).setup(spy, **kwargs)
+
+        new_calls = []
+
+        # Convert any operations into fake functions.
+        #
+        # Note that we have to force the resulting fake function to be
+        # unbound, since all fake functions provided to an operation are
+        # called without an owner.
+        for call in self._calls:
+            if 'op' in call:
+                call = call.copy()
+                call['call_fake'] = call.pop('op').setup(spy,
+                                                         force_unbound=True)
+
+            new_calls.append(call)
+
+        self._calls = new_calls
+
+        return result
+
     def get_call_match_config(self, spy_call):
         """Return a call match configuration for a call.
 
@@ -193,9 +251,18 @@ class BaseMatchingSpyOperation(BaseSpyOperation):
         """Handle a call to this operation.
 
         This will find a suitable call match configuration, if one was
-        provided, and then call either the fake function (if ``call_fake`` was
-        provided), the original function (if ``call_original`` is not set to
-        ``False``), or return ``None``.
+        provided, and then call one of the following, in order of preference:
+
+        1. The spy operation (if ``op`` is set)
+        2. The fake function (if ``call_fake`` was provided)
+        3. The original function (if ``call_original`` is not set to
+           ``False``)
+
+        If none of the above are invoked, ``None`` will be returned instead.
+
+        Version Changed:
+            6.1:
+            Added support for ``op``.
 
         Args:
             spy_call (kgb.calls.SpyCall):
@@ -363,7 +430,14 @@ class SpyOpMatchInOrder(BaseMatchingSpyOperation):
                     'secret_button_pushed': True,
                 },
                 'call_original': True,
-            }
+            },
+            {
+                'args': (4, 8, 15, 16, 23, 42),
+                'kwargs': {
+                    'secret_button_pushed': True,
+                },
+                'op': SpyOpRaise(Exception('Oh no')),
+            },
         ]))
     """
 
@@ -389,6 +463,10 @@ class SpyOpMatchInOrder(BaseMatchingSpyOperation):
             Whether to call the original function. This is the default if
             ``call_fake`` is not provided.
 
+        ``op`` (:py:class:`BaseSpyOperation`, optional):
+            A spy operation to call instead of ``call_fake`` or
+            ``call_original``.
+
         Args:
             calls (list of dict):
                 A list of call match configurations.
diff --git a/kgb/tests/base.py b/kgb/tests/base.py
index d4c284c39cf95d7480da27790bb30f5670c4817e..c64bfba92816e3747f1f60c38dbd59a4104bca9d 100644
--- a/kgb/tests/base.py
+++ b/kgb/tests/base.py
@@ -14,7 +14,6 @@ from kgb.agency import SpyAgency
 
 class MathClass(object):
     def do_math(self, a=1, b=2, *args, **kwargs):
-        print(self)
         assert isinstance(self, MathClass)
 
         return a + b
diff --git a/kgb/tests/test_ops.py b/kgb/tests/test_ops.py
index 9bf487fa769f5a33d552e0c2d5e95a9013f8b8b2..36d64df27482c87b182a7c54c569355fd55810b2 100644
--- a/kgb/tests/test_ops.py
+++ b/kgb/tests/test_ops.py
@@ -30,6 +30,42 @@ class SpyOpMatchAnyTests(TestCase):
 
         self.assertEqual(obj.do_math(a=1, b=2), -1)
 
+    def test_setup_with_instance_and_op(self):
+        """Testing SpyOpMatchAny set up with op=SpyOpMatchAny([...]) and op"""
+        obj = MathClass()
+
+        self.agency.spy_on(
+            obj.do_math,
+            op=SpyOpMatchAny([
+                {
+                    'kwargs': {
+                        'a': 1,
+                        'b': 2,
+                    },
+                    'op': SpyOpMatchInOrder([
+                        {
+                            'kwargs': {
+                                'a': 1,
+                                'b': 2,
+                                'x': 1,
+                            },
+                            'op': SpyOpReturn(123),
+                        },
+                        {
+                            'kwargs': {
+                                'a': 1,
+                                'b': 2,
+                                'x': 2,
+                            },
+                            'op': SpyOpReturn(456),
+                        },
+                    ]),
+                },
+            ]))
+
+        self.assertEqual(obj.do_math(a=1, b=2, x=1), 123)
+        self.assertEqual(obj.do_math(a=1, b=2, x=2), 456)
+
     def test_with_function(self):
         """Testing SpyOpMatchAny with function"""
         def do_math(a, b):
@@ -46,6 +82,38 @@ class SpyOpMatchAnyTests(TestCase):
 
         self.assertEqual(do_math(5, 3), 2)
 
+    def test_with_function_and_op(self):
+        """Testing SpyOpMatchAny with function and op"""
+        def do_math(a, b, x=0):
+            return a + b
+
+        self.agency.spy_on(
+            do_math,
+            op=SpyOpMatchAny([
+                {
+                    'args': [5, 3],
+                    'op': SpyOpMatchInOrder([
+                        {
+                            'args': [5, 3],
+                            'kwargs': {
+                                'x': 1,
+                            },
+                            'op': SpyOpReturn(123),
+                        },
+                        {
+                            'args': [5, 3],
+                            'kwargs': {
+                                'x': 2,
+                            },
+                            'op': SpyOpReturn(456),
+                        },
+                    ]),
+                },
+            ]))
+
+        self.assertEqual(do_math(a=5, b=3, x=1), 123)
+        self.assertEqual(do_math(a=5, b=3, x=2), 456)
+
     def test_with_classmethod(self):
         """Testing SpyOpMatchAny with classmethod"""
         self.agency.spy_on(
@@ -63,6 +131,41 @@ class SpyOpMatchAnyTests(TestCase):
 
         self.assertEqual(MathClass.class_do_math(a=5, b=3), 2)
 
+    def test_with_classmethod_and_op(self):
+        """Testing SpyOpMatchAny with classmethod and op"""
+        self.agency.spy_on(
+            MathClass.class_do_math,
+            owner=MathClass,
+            op=SpyOpMatchAny([
+                {
+                    'kwargs': {
+                        'a': 5,
+                        'b': 3,
+                    },
+                    'op': SpyOpMatchInOrder([
+                        {
+                            'kwargs': {
+                                'a': 5,
+                                'b': 3,
+                                'x': 1,
+                            },
+                            'op': SpyOpReturn(123),
+                        },
+                        {
+                            'kwargs': {
+                                'a': 5,
+                                'b': 3,
+                                'x': 2,
+                            },
+                            'op': SpyOpReturn(456),
+                        },
+                    ]),
+                },
+            ]))
+
+        self.assertEqual(MathClass.class_do_math(a=5, b=3, x=1), 123)
+        self.assertEqual(MathClass.class_do_math(a=5, b=3, x=2), 456)
+
     def test_with_unbound_method(self):
         """Testing SpyOpMatchAny with unbound method"""
         self.agency.spy_on(
@@ -78,9 +181,44 @@ class SpyOpMatchAnyTests(TestCase):
             ]))
 
         obj = MathClass()
-
         self.assertEqual(obj.do_math(a=4, b=3), 7)
 
+    def test_with_unbound_method_and_op(self):
+        """Testing SpyOpMatchAny with unbound method and op"""
+        self.agency.spy_on(
+            MathClass.do_math,
+            owner=MathClass,
+            op=SpyOpMatchAny([
+                {
+                    'kwargs': {
+                        'a': 4,
+                        'b': 3,
+                    },
+                    'op': SpyOpMatchInOrder([
+                        {
+                            'kwargs': {
+                                'a': 4,
+                                'b': 3,
+                                'x': 1,
+                            },
+                            'op': SpyOpReturn(123),
+                        },
+                        {
+                            'kwargs': {
+                                'a': 4,
+                                'b': 3,
+                                'x': 2,
+                            },
+                            'op': SpyOpReturn(456),
+                        },
+                    ]),
+                },
+            ]))
+
+        obj = MathClass()
+        self.assertEqual(obj.do_math(a=4, b=3, x=1), 123)
+        self.assertEqual(obj.do_math(a=4, b=3, x=2), 456)
+
     def test_with_expected_calls(self):
         """Testing SpyOpMatchAny with all expected calls"""
         obj = MathClass()
@@ -103,6 +241,30 @@ class SpyOpMatchAnyTests(TestCase):
                 },
                 {
                     'kwargs': {
+                        'a': 100,
+                        'b': 200,
+                    },
+                    'op': SpyOpMatchInOrder([
+                        {
+                            'kwargs': {
+                                'a': 100,
+                                'b': 200,
+                                'x': 1,
+                            },
+                            'op': SpyOpReturn(123),
+                        },
+                        {
+                            'kwargs': {
+                                'a': 100,
+                                'b': 200,
+                                'x': 2,
+                            },
+                            'op': SpyOpReturn(456),
+                        },
+                    ]),
+                },
+                {
+                    'kwargs': {
                         'a': 5,
                         'b': 9,
                     },
@@ -117,11 +279,13 @@ class SpyOpMatchAnyTests(TestCase):
         values = [
             obj.do_math(5, b=9),
             obj.do_math(a=2, b=8),
+            obj.do_math(a=100, b=200, x=1),
+            obj.do_math(a=100, b=200, x=2),
             obj.do_math(a=1, b=1),
             obj.do_math(4, 7),
         ]
 
-        self.assertEqual(values, [24, None, 1001, 11])
+        self.assertEqual(values, [24, None, 123, 456, 1001, 11])
 
     def test_with_unexpected_call(self):
         """Testing SpyOpMatchAny with unexpected call"""
@@ -166,6 +330,26 @@ class SpyOpMatchInOrderTests(TestCase):
 
         self.assertEqual(obj.do_math(a=1, b=2), 3)
 
+    def test_setup_with_instance_and_op(self):
+        """Testing SpyOpMatchInOrder set up with op=SpyOpMatchInOrder([...])
+        and op
+        """
+        obj = MathClass()
+
+        self.agency.spy_on(
+            obj.do_math,
+            op=SpyOpMatchInOrder([
+                {
+                    'kwargs': {
+                        'a': 1,
+                        'b': 2,
+                    },
+                    'op': SpyOpReturn(123),
+                },
+            ]))
+
+        self.assertEqual(obj.do_math(a=1, b=2), 123)
+
     def test_with_function(self):
         """Testing SpyOpMatchInOrder with function"""
         def do_math(a, b):
@@ -182,6 +366,22 @@ class SpyOpMatchInOrderTests(TestCase):
 
         self.assertEqual(do_math(5, 3), 2)
 
+    def test_with_function_and_op(self):
+        """Testing SpyOpMatchInOrder with function and op"""
+        def do_math(a, b):
+            return a + b
+
+        self.agency.spy_on(
+            do_math,
+            op=SpyOpMatchInOrder([
+                {
+                    'args': [5, 3],
+                    'op': SpyOpReturn(123),
+                },
+            ]))
+
+        self.assertEqual(do_math(5, 3), 123)
+
     def test_with_classmethod(self):
         """Testing SpyOpMatchInOrder with classmethod"""
         self.agency.spy_on(
@@ -199,6 +399,23 @@ class SpyOpMatchInOrderTests(TestCase):
 
         self.assertEqual(MathClass.class_do_math(a=5, b=3), 2)
 
+    def test_with_classmethod_and_op(self):
+        """Testing SpyOpMatchInOrder with classmethod and op"""
+        self.agency.spy_on(
+            MathClass.class_do_math,
+            owner=MathClass,
+            op=SpyOpMatchInOrder([
+                {
+                    'kwargs': {
+                        'a': 5,
+                        'b': 3,
+                    },
+                    'op': SpyOpReturn(123),
+                },
+            ]))
+
+        self.assertEqual(MathClass.class_do_math(a=5, b=3), 123)
+
     def test_with_unbound_method(self):
         """Testing SpyOpMatchInOrder with unbound method"""
         self.agency.spy_on(
@@ -217,6 +434,25 @@ class SpyOpMatchInOrderTests(TestCase):
 
         self.assertEqual(obj.do_math(a=4, b=3), 7)
 
+    def test_with_unbound_method_and_op(self):
+        """Testing SpyOpMatchInOrder with unbound method and op"""
+        self.agency.spy_on(
+            MathClass.do_math,
+            owner=MathClass,
+            op=SpyOpMatchInOrder([
+                {
+                    'kwargs': {
+                        'a': 4,
+                        'b': 3,
+                    },
+                    'op': SpyOpReturn(123),
+                },
+            ]))
+
+        obj = MathClass()
+
+        self.assertEqual(obj.do_math(a=4, b=3), 123)
+
     def test_with_expected_calls(self):
         """Testing SpyOpMatchInOrder with all expected calls"""
         obj = MathClass()
@@ -239,6 +475,13 @@ class SpyOpMatchInOrderTests(TestCase):
                 },
                 {
                     'kwargs': {
+                        'a': 100,
+                        'b': 200,
+                    },
+                    'op': SpyOpReturn(123),
+                },
+                {
+                    'kwargs': {
                         'a': 5,
                         'b': 9,
                     },
@@ -252,11 +495,12 @@ class SpyOpMatchInOrderTests(TestCase):
         values = [
             obj.do_math(4, 7),
             obj.do_math(a=2, b=8),
+            obj.do_math(a=100, b=200),
             obj.do_math(5, b=9),
             obj.do_math(a=1, b=1),
         ]
 
-        self.assertEqual(values, [11, None, 24, 1001])
+        self.assertEqual(values, [11, None, 123, 24, 1001])
 
     def test_with_unexpected_call(self):
         """Testing SpyOpMatchInOrder with unexpected call"""
