diff --git a/SUMMARY.md b/SUMMARY.md
index 3b41ee1..ba3a98d 100644
--- a/SUMMARY.md
+++ b/SUMMARY.md
@@ -10,6 +10,7 @@
* [How Configuring the Command Processor Works](/contents/HowConfiguringTheCommandProcessorWorks.md)
* [How Configuring a Dispatcher for an External Bus Works](/contents/HowConfiguringTheDispatcherWorks.md)
* [InMemory Options for Development and Testing](/contents/InMemoryOptions.md)
+* [Test Double Options for Command Processor](/contents/TestDoubleOptions.md)
## Darker Configuration
diff --git a/contents/TestDoubleOptions.md b/contents/TestDoubleOptions.md
new file mode 100644
index 0000000..36cd89b
--- /dev/null
+++ b/contents/TestDoubleOptions.md
@@ -0,0 +1,263 @@
+# Test Double Options for Command Processor
+
+## Overview
+
+When handlers depend on `IAmACommandProcessor` to publish events or send commands, you need a way to verify those interactions in tests. The `Paramore.Brighter.Testing` package provides `SpyCommandProcessor` - a test double that records all calls for later verification.
+
+## Installation
+
+Add a reference to the `Paramore.Brighter.Testing` package in your test project:
+
+```xml
+
+```
+
+Or reference the project directly:
+
+```xml
+
+```
+
+## Using SpyCommandProcessor
+
+### Basic Usage
+
+Inject `SpyCommandProcessor` as your `IAmACommandProcessor` dependency and verify interactions after exercising the handler:
+
+```csharp
+// Arrange
+var spy = new SpyCommandProcessor();
+var handler = new PlaceOrderHandler(spy);
+var command = new PlaceOrder { ProductId = "WIDGET-1", Quantity = 3 };
+
+// Act
+handler.Handle(command);
+
+// Assert - verify the handler published an OrderPlaced event
+spy.WasCalled(CommandType.Publish).ShouldBeTrue();
+var published = spy.Observe();
+published.ProductId.ShouldBe("WIDGET-1");
+```
+
+### API Layers
+
+SpyCommandProcessor provides a layered API, from simple checks to detailed inspection:
+
+#### Layer 1: Quick Checks
+
+For the most common verification needs:
+
+```csharp
+// Was a specific method type called?
+spy.WasCalled(CommandType.Send) // true/false
+spy.WasCalled(CommandType.Publish) // true/false
+spy.WasCalled(CommandType.Post) // true/false
+
+// How many times?
+spy.CallCount(CommandType.Send) // int
+
+// Dequeue requests in FIFO order (consuming)
+var command = spy.Observe();
+var @event = spy.Observe();
+```
+
+`Observe()` dequeues the next request of type `T` from the queue. This is useful for verifying multiple calls in sequence. It throws `InvalidOperationException` if no matching request is found.
+
+#### Layer 2: Request Inspection
+
+For examining all calls without consuming them:
+
+```csharp
+// Get all requests of a specific type (non-destructive, can call repeatedly)
+IEnumerable commands = spy.GetRequests();
+
+// Get all recorded calls for a command type (includes timestamp and context)
+IEnumerable sendCalls = spy.GetCalls(CommandType.Send);
+
+// Get the sequence of command types in call order
+IReadOnlyList types = spy.Commands;
+// e.g. [Send, Publish, Send] after three calls
+```
+
+#### Layer 3: Full Details
+
+For advanced scenarios requiring complete call information:
+
+```csharp
+// All recorded calls with full details
+IReadOnlyList allCalls = spy.RecordedCalls;
+
+foreach (var call in allCalls)
+{
+ Console.WriteLine($"{call.Type} at {call.Timestamp}: {call.Request.GetType().Name}");
+ // call.Context provides the RequestContext if one was passed
+}
+```
+
+### CommandType Values
+
+Each `IAmACommandProcessor` method maps to a `CommandType`:
+
+| Method | CommandType |
+|--------|-------------|
+| `Send` | `Send` |
+| `SendAsync` | `SendAsync` |
+| `Publish` | `Publish` |
+| `PublishAsync` | `PublishAsync` |
+| `Post` | `Post` |
+| `PostAsync` | `PostAsync` |
+| `DepositPost` | `Deposit` |
+| `DepositPostAsync` | `DepositAsync` |
+| `ClearOutbox` | `Clear` |
+| `ClearOutboxAsync` | `ClearAsync` |
+| `Call` | `Call` |
+| Scheduled (sync) | `Scheduler` |
+| Scheduled (async) | `SchedulerAsync` |
+
+## Verifying Send/Publish/Post Calls
+
+### Verifying Send
+
+```csharp
+var spy = new SpyCommandProcessor();
+var handler = new MyHandler(spy);
+
+handler.Handle(new TriggerCommand());
+
+// Quick check
+spy.WasCalled(CommandType.Send).ShouldBeTrue();
+
+// Inspect the sent command
+var sent = spy.Observe();
+sent.SomeProperty.ShouldBe("expected");
+```
+
+### Verifying Publish
+
+```csharp
+var spy = new SpyCommandProcessor();
+var handler = new OrderHandler(spy);
+
+handler.Handle(new PlaceOrder { OrderId = "ORD-1" });
+
+// Check event was published
+spy.CallCount(CommandType.Publish).ShouldBe(1);
+
+// Inspect the event
+var events = spy.GetRequests();
+events.First().OrderId.ShouldBe("ORD-1");
+```
+
+### Verifying Post (External Bus)
+
+```csharp
+var spy = new SpyCommandProcessor();
+var handler = new NotificationHandler(spy);
+
+handler.Handle(new SendNotification { UserId = "user-1" });
+
+spy.WasCalled(CommandType.Post).ShouldBeTrue();
+var posted = spy.Observe();
+posted.UserId.ShouldBe("user-1");
+```
+
+## Verifying the Outbox Pattern (DepositPost + ClearOutbox)
+
+When handlers use the outbox pattern, `SpyCommandProcessor` tracks deposits separately:
+
+```csharp
+var spy = new SpyCommandProcessor();
+var handler = new TransactionalHandler(spy);
+
+handler.Handle(new ProcessPayment { Amount = 99.99m });
+
+// Verify the deposit
+spy.WasCalled(CommandType.Deposit).ShouldBeTrue();
+spy.DepositedRequests.Count.ShouldBe(1);
+
+// Get the deposited request
+var (id, request) = spy.DepositedRequests.First();
+var deposited = request.ShouldBeOfType();
+deposited.Amount.ShouldBe(99.99m);
+
+// Simulate ClearOutbox (moves deposits to observation queue)
+spy.ClearOutbox(new[] { id });
+
+// Now it's available via Observe
+var cleared = spy.Observe();
+cleared.Amount.ShouldBe(99.99m);
+```
+
+## State Management
+
+Use `Reset()` between test scenarios when reusing a spy:
+
+```csharp
+var spy = new SpyCommandProcessor();
+
+// First scenario
+spy.Send(new CommandA());
+spy.WasCalled(CommandType.Send).ShouldBeTrue();
+
+// Reset for next scenario
+spy.Reset();
+
+spy.WasCalled(CommandType.Send).ShouldBeFalse();
+spy.RecordedCalls.Count.ShouldBe(0);
+spy.DepositedRequests.Count.ShouldBe(0);
+```
+
+## Extending SpyCommandProcessor
+
+All methods on `SpyCommandProcessor` are `virtual`, allowing you to create specialized subclasses:
+
+```csharp
+public class ThrowingSpyCommandProcessor : SpyCommandProcessor
+{
+ public override void Send(TRequest command, RequestContext? requestContext = null)
+ {
+ base.Send(command, requestContext); // Still records the call
+ throw new InvalidOperationException("Send should not be called in this test");
+ }
+}
+```
+
+This is useful for testing error handling paths in your handlers.
+
+## Alternative: Using Mocking Frameworks
+
+If you prefer mocking frameworks, you can mock `IAmACommandProcessor` directly. `SpyCommandProcessor` is a convenience for when you want a lightweight, framework-independent test double.
+
+### Moq
+
+```csharp
+var mock = new Mock();
+var handler = new MyHandler(mock.Object);
+
+handler.Handle(new MyCommand());
+
+mock.Verify(p => p.Publish(It.IsAny(), It.IsAny()), Times.Once);
+```
+
+### NSubstitute
+
+```csharp
+var substitute = Substitute.For();
+var handler = new MyHandler(substitute);
+
+handler.Handle(new MyCommand());
+
+substitute.Received(1).Publish(Arg.Any(), Arg.Any());
+```
+
+## Best Practices
+
+1. **Prefer `SpyCommandProcessor` over mocking frameworks** for simple verification - it requires no additional dependencies and provides a clearer API.
+
+2. **Use `Observe()`** for sequential verification when order matters. Use `GetRequests()` when you just need all requests of a type.
+
+3. **Use `Reset()`** if sharing a spy across multiple test methods in a fixture rather than creating new instances. Creating a new instance per test class constructor is preferred.
+
+4. **Test behaviors, not interactions** - verify that the right events/commands were produced with the right data, rather than asserting on exact call sequences.
+
+5. **Extend with subclasses** when you need the spy to throw exceptions or return specific values from `Call()`.