githubEdit

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:

<PackageReference Include="Paramore.Brighter.Testing" />

Or reference the project directly:

<ProjectReference Include="..\..\src\Paramore.Brighter.Testing\Paramore.Brighter.Testing.csproj" />

Using SpyCommandProcessor

Basic Usage

Inject SpyCommandProcessor as your IAmACommandProcessor dependency and verify interactions after exercising the handler:

// 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<OrderPlaced>();
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:

Observe<T>() 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:

Layer 3: Full Details

For advanced scenarios requiring complete call information:

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

Verifying Publish

Verifying Post (External Bus)

Verifying the Outbox Pattern (DepositPost + ClearOutbox)

When handlers use the outbox pattern, SpyCommandProcessor tracks deposits separately:

State Management

Use Reset() between test scenarios when reusing a spy:

Extending SpyCommandProcessor

All methods on SpyCommandProcessor are virtual, allowing you to create specialized subclasses:

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

NSubstitute

Best Practices

  1. Prefer SpyCommandProcessor over mocking frameworks for simple verification - it requires no additional dependencies and provides a clearer API.

  2. Use Observe<T>() for sequential verification when order matters. Use GetRequests<T>() 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<T, TResponse>().

Last updated

Was this helpful?