CleanArch.DevKit.Mediator.Testing 1.1.1

dotnet add package CleanArch.DevKit.Mediator.Testing --version 1.1.1
                    
NuGet\Install-Package CleanArch.DevKit.Mediator.Testing -Version 1.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CleanArch.DevKit.Mediator.Testing" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CleanArch.DevKit.Mediator.Testing" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="CleanArch.DevKit.Mediator.Testing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CleanArch.DevKit.Mediator.Testing --version 1.1.1
                    
#r "nuget: CleanArch.DevKit.Mediator.Testing, 1.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CleanArch.DevKit.Mediator.Testing@1.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CleanArch.DevKit.Mediator.Testing&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=CleanArch.DevKit.Mediator.Testing&version=1.1.1
                    
Install as a Cake Tool

CleanArch.DevKit.Mediator.Testing

Helpers de test pour le médiateur — FakeMediator configurable + MediatorHarness pour le vrai pipeline, sans Moq ni NSubstitute.

Rôle

Deux outils complémentaires :

  • FakeMediator — implémentation IMediator en mémoire pour tester du code consommateur du médiateur (endpoints, services applicatifs). Stub des réponses, captures pour assertions.
  • MediatorHarness — harnais d'intégration qui exécute le vrai pipeline (vrais handlers + behaviors via DI) tout en capturant les requêtes, notifications et streams. Idéal pour tester un handler complet avec sa chaîne de behaviors (validation, logging, etc.).

Cohérent avec la philosophie zero-reflection du toolkit : aucun framework de mock requis.

Installation

dotnet add package CleanArch.DevKit.Mediator.Testing

Dépend uniquement de CleanArch.DevKit.Mediator. MediatorHarness requiert que le projet de test consomme aussi le package CleanArch.DevKit.Mediator (pour que le générateur produise AddMediator()).

Quand utiliser quoi

Cas d'usage Outil
Tester un service qui envoie des requêtes au mediator FakeMediator
Tester un handler isolé du pipeline construction directe du handler
Tester un handler avec ses behaviors (validation, logging, …) MediatorHarness
Tester qu'un handler publie une notification MediatorHarness + CaptureNotifications<T>()
Tester du code qui mock le mediator sans démarrer la DI FakeMediator

Modes de fonctionnement

var mediator = new FakeMediator();              // mode tolérant (défaut)
var mediator = new FakeMediator(strict: true);  // mode strict
  • Tolérant — toute requête sans setup retourne default(TResponse) (Send) ou une séquence vide (CreateStream).
  • Strict — toute requête sans setup lève FakeMediatorMissingSetupException.

Publish n'est jamais affecté par le mode : les notifications n'ont pas de "handler manquant", elles sont juste enregistrées.


Setup des réponses

Send avec valeur

mediator.Setup<GetOrder, Order>().Returns(new Order(1, "fixed"));

Send avec valeur calculée

mediator.Setup<GetOrder, Order>().Returns(req => new Order(req.Id, $"order-{req.Id}"));

Send qui lève une exception

mediator.Setup<GetOrder, Order>().Throws(new InvalidOperationException("boom"));
mediator.Setup<GetOrder, Order>().Throws<InvalidOperationException>();

Command (IRequest sans retour)

mediator.Setup<ShipOrder>().Completes();                            // succès silencieux
mediator.Setup<ShipOrder>().Throws(new ConflictException("..."));   // erreur

Stream

mediator.SetupStream<StreamOrders, Order>().Yields(o1, o2, o3);
mediator.SetupStream<StreamOrders, Order>().Yields(req => Enumerable.Range(0, req.Count).Select(...));
mediator.SetupStream<StreamOrders, Order>().Throws(new InvalidOperationException("boom"));

Plusieurs Setup pour le même type de requête : le dernier gagne.


Assertions

// Requêtes envoyées via Send, dans l'ordre d'invocation
IReadOnlyList<ShipOrder> ships = mediator.Sent<ShipOrder>();

// Notifications publiées via Publish
IReadOnlyList<OrderShipped> events = mediator.Published<OrderShipped>();

// Stream requests dispatchés via CreateStream
IReadOnlyList<StreamOrders> streams = mediator.Streamed<StreamOrders>();

Le filtre par type est polymorphique : Sent<IRequest>() retourne toutes les requêtes envoyées, Published<INotification>() toutes les notifications, etc.


Exemple complet

public sealed class OrderShipmentService
{
    private readonly IMediator _mediator;
    public OrderShipmentService(IMediator mediator) => _mediator = mediator;

    public async Task ShipAsync(int orderId, CancellationToken ct)
    {
        var order = await _mediator.Send(new GetOrder(orderId), ct);
        if (order is null) return;

        await _mediator.Send(new MarkShipped(orderId), ct);
        await _mediator.Publish(new OrderShipped(orderId), ct);
    }
}

[Fact]
public async Task Ship_PublishesOrderShipped()
{
    var mediator = new FakeMediator();
    mediator.Setup<GetOrder, Order?>().Returns(new Order(42, "Widget"));
    mediator.Setup<MarkShipped>().Completes();

    var sut = new OrderShipmentService(mediator);
    await sut.ShipAsync(42, CancellationToken.None);

    Assert.Single(mediator.Sent<MarkShipped>());
    Assert.Single(mediator.Published<OrderShipped>());
    Assert.Equal(42, mediator.Published<OrderShipped>()[0].OrderId);
}

Reset entre les tests

Les fixtures partagées (IClassFixture, ICollectionFixture) peuvent réutiliser une instance de FakeMediator en appelant Reset() entre chaque test pour vider à la fois les invocations enregistrées et les setups :

mediator.Reset();

Limites de FakeMediator

  • Pas de simulation du pipeline — les IPipelineBehavior<,> enregistrés dans le vrai mediator (Logging, Validation, etc.) ne s'exécutent pas. Pour ça → MediatorHarness.
  • Match exact du type de requête — un setup sur BaseQuery n'attrape pas une requête DerivedQuery. Configurer chaque type concret.
  • Pas de matching par prédicat — pour différencier deux instances de la même requête, utiliser Returns(req => ...) qui reçoit la requête en paramètre.

MediatorHarness — tester avec le vrai pipeline

Principe

MediatorHarness construit un ServiceProvider personnalisé, exécute les vrais handlers à travers la vraie chaîne de behaviors, et capture chaque Send/Publish/CreateStream pour assertions.

await using var harness = MediatorHarness.Create(b =>
{
    b.Services.AddMediator();                                       // vrai mediator source-généré
    b.Services.AddSingleton<IOrderRepo, FakeOrderRepo>();           // tes fakes/stubs côté infra
    b.Services.AddLoggingBehavior();                                // les behaviors que tu veux
    b.CaptureNotifications<OrderShipped>();                         // opt-in par type
});

var result = await harness.Send(new ShipOrder(42));

Assert.Single(harness.Sent<ShipOrder>());
Assert.Single(harness.Published<OrderShipped>());
Assert.Equal(42, harness.Published<OrderShipped>()[0].OrderId);

API

public sealed class MediatorHarness : IAsyncDisposable, IDisposable
{
    public static MediatorHarness Create(Action<MediatorHarnessBuilder> configure);
    public IServiceProvider Services { get; }

    public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken ct = default);
    public Task Publish<TNotification>(TNotification notification, CancellationToken ct = default) where TNotification : INotification;
    public IAsyncEnumerable<TResponse> CreateStream<TResponse>(IStreamRequest<TResponse> request, CancellationToken ct = default);

    public IReadOnlyList<TRequest> Sent<TRequest>();
    public IReadOnlyList<TNotification> Published<TNotification>() where TNotification : INotification;
    public IReadOnlyList<TRequest> Streamed<TRequest>();

    public void Reset();      // vide les enregistrements, conserve la DI
}

public sealed class MediatorHarnessBuilder
{
    public IServiceCollection Services { get; }
    public MediatorHarnessBuilder CaptureNotifications<TNotification>() where TNotification : INotification;
}

Capture des notifications

Les notifications ne sont enregistrées que pour les types explicitement opt-in via CaptureNotifications<T>(). Cette opt-in installe un INotificationHandler<T> dans le conteneur DI qui pousse chaque notification dans la liste de capture.

  • Les notifications publiées par le test (harness.Publish(...)) sont capturées.
  • Les notifications publiées depuis un handler (_mediator.Publish(...) dans le code testé) sont aussi capturées — c'est tout l'intérêt de passer par DI.
  • Les notifications non opt-in ne sont pas enregistrées, mais elles s'exécutent quand même normalement (leurs vrais handlers tournent si présents).

Capture des requêtes

Toutes les requêtes envoyées via Send (depuis le test ou depuis un handler appelant _mediator.Send(...)) sont capturées automatiquement via un IPipelineBehavior<,> open-generic enregistré par le harnais.

Capture des streams

Les streams envoyés via harness.CreateStream(...) sont enregistrés au niveau du harnais. Les streams déclenchés depuis un handler ne sont pas interceptés (il n'existe pas de pipeline behavior pour les streams dans Mediator.Core).

Wiring du projet de test

Pour que AddMediator() voie les handlers du projet de test, le générateur source doit y être branché. Dans le csproj de test, déclarer :

<ItemGroup>
  <PackageReference Include="CleanArch.DevKit.Mediator" />
  <PackageReference Include="CleanArch.DevKit.Mediator.Testing" />
  
</ItemGroup>

Et ajouter dans le projet de test un fichier Mediator.cs :

namespace MyApp.Tests;
public partial class Mediator { }

Le générateur peuple cette classe à la compilation. C'est la même contrainte que dans un projet consommateur normal.

Limites de MediatorHarness

  • Nécessite que le générateur source tourne dans le projet de test — voir la section Wiring ci-dessus.
  • Pas de capture de stream interne au handler — Mediator.Core n'expose pas de pipeline behavior pour les streams.
  • Pas de mocking partiel — pour ne stubber que certaines requêtes et exécuter les autres pour de vrai, utiliser FakeMediator à la place.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 87 5/17/2026
1.1.0 87 5/17/2026
1.0.0 93 5/15/2026