Eternet.Mediator.Testing.Generator 3.1.7

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Eternet.Mediator.Testing.Generator --version 3.1.7
                    
NuGet\Install-Package Eternet.Mediator.Testing.Generator -Version 3.1.7
                    
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="Eternet.Mediator.Testing.Generator" Version="3.1.7">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Eternet.Mediator.Testing.Generator" Version="3.1.7" />
                    
Directory.Packages.props
<PackageReference Include="Eternet.Mediator.Testing.Generator">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
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 Eternet.Mediator.Testing.Generator --version 3.1.7
                    
#r "nuget: Eternet.Mediator.Testing.Generator, 3.1.7"
                    
#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 Eternet.Mediator.Testing.Generator@3.1.7
                    
#: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=Eternet.Mediator.Testing.Generator&version=3.1.7
                    
Install as a Cake Addin
#tool nuget:?package=Eternet.Mediator.Testing.Generator&version=3.1.7
                    
Install as a Cake Tool

Eternet.Mediator.Testing.Generator

Eternet.Mediator.Testing.Generator es un source generator completamente separado que genera código para testing de pipelines mediator.

Tabla de Contenidos


Filosofía

  • Generador de Producción (Eternet.Mediator.Generator): Genera PipelineExecutor, StepsResults, etc.
  • Generador de Testing (Eternet.Mediator.Testing.Generator): Genera PipelineTestBase, interfaces de override, etc.

Beneficios:

Cero overhead en producción - El código de testing no se compila en ejecutables de prod.

Claridad - Cada generador tiene una responsabilidad clara

Testabilidad - Los generadores se pueden testear independientemente


Estrategia de Testing

El Testing Generator está diseñado para complementar los unit tests de steps, no para reemplazarlos.

¿Cuándo usar cada herramienta?

Si quieres verificar... Usa...
Lógica de validación de un paso Unit test del step
Edge cases de transformación Unit test del step
Que StepsResults acumula correctamente Pipeline test
Que un error en paso N detiene el pipeline Pipeline test
Que el ChangeTracker tiene las entidades esperadas Pipeline test
Que ValidationBehavior rechaza requests inválidos Integration test

Evitando Redundancia

Si tenés 100% coverage en cada step, el pipeline test debe demostrar únicamente:

  1. Un happy path que verifica StepsResults y la respuesta final
  2. Uno o dos error paths que verifican propagación de errores
  3. Para EF Core: estado del ChangeTracker antes de SaveChangesAsync

💡 Regla de Oro: No testees en el pipeline lo que ya testeaste en los unit tests de steps.

Instalación

dotnet add package Eternet.Mediator.Testing.Generator

Breaking Changes in 3.0.0

Eternet.Mediator.Testing.Generator 3.0.0 mirrors the new generated runtime model.

  • Generated test bases no longer expose or keep a public ScopedStates field.
  • Test infrastructure now relies on the framework-owned generated runtime helpers instead of the retired public bag API.
  • Existing custom tests that manually used ScopedStates should migrate to request.StepsResults and, when needed, GeneratedBindingRuntime.

Migration guide: ../docs/scoped-states-breaking-change-v3.md


Uso Básico

1. Decorar el handler con atributos

[GenerateExecutePipeline<Step1>]
[GenerateExecutePipeline<Step2>]
[GenerateExecutePipeline<Step3>]
[GeneratePipelineTestBase]  // ← Activa la generación del Testing Generator
public class MyHandler : DomainResultHandlerAsync<MyHandler.Request, MyHandler.Response>
{
    public override async ValueTask<Response> Handle(Request request, CancellationToken ct)
    {
        var step1Result = request.StepsResults.Step1Result;
        var step2Result = request.StepsResults.Step2Result;
        var step3Result = request.StepsResults.Step3Result;
        
        return new Response(...);
    }
}

2. Generar la clase base de testing

El Testing Generator crea automáticamente:

// Generado automáticamente por Eternet.Mediator.Testing.Generator
public abstract class MyHandlerPipelineTestBase : BaseHandlerResponses
{
    // Constructor que configura todos los servicios
    protected MyHandlerPipelineTestBase() { ... }

    // Métodos para ejecutar el pipeline
    protected Task<MyHandler.Response> ExecutePipelineAsync(Request request);
    protected Task<MyHandler.Response> ExecuteWithMediatorAsync(Request request);

    // Métodos virtuales para cada paso
    protected virtual Task<Step1Result> ExecuteStep1Async(...);
    protected virtual Task<Step2Result> ExecuteStep2Async(...);
    protected virtual Task<Step3Result> ExecuteStep3Async(...);

    // Interfaces para override
    // IOverrideStep1, IOverrideStep2, IOverrideStep3
    // IOverrideMyHandlerSteps (todos los pasos)
}

3. Escribir tests heredando de la clase base

public class MyHandlerTests : MyHandlerPipelineTestBase
{
    [Fact]
    public async Task Should_ExecutePipeline_Successfully()
    {
        var request = new MyHandler.Request(...);
        var response = await ExecutePipelineAsync(request, TestContext.Current.CancellationToken);
        
        response.IsDomainResult.Should().BeTrue();
    }
}

Generación de Código

Atributo [GeneratePipelineTestBase]

El atributo [GeneratePipelineTestBase] es generado por Eternet.Mediator.Generator (el generador de producción), NO por este generador de testing. Esto permite marcar handlers para testing sin agregar dependencias de testing al código de producción:

// Generado por Eternet.Mediator.Generator en: EternetMediatorAttributes.g.cs
namespace Eternet.Mediator
{
    /// <summary>
    /// Marks a pipeline handler to generate a test base class.
    /// The test base class is generated by Eternet.Mediator.Testing.Generator.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class GeneratePipelineTestBaseAttribute : Attribute;
}

MediatorTestingGenerator

Analiza la clase decorada y genera:

  1. Interfaces de Override (una por paso + una general):

    • IOverrideStep1
    • IOverrideStep2
    • IOverrideStep3
    • IOverrideMyHandlerSteps (todos los pasos)
  2. Clase Base Abstracta:

    • MyHandlerPipelineTestBase
    • Hereda de BaseHandlerResponses
    • Configura automáticamente servicios en el constructor
    • Proporciona ExecutePipelineAsync y ExecuteWithMediatorAsync
    • Implementa métodos virtuales para cada paso
  3. NoOpHybridCache (shared):

    • Implementación no-op de IHybridCache
    • Se genera una sola vez en Eternet.Mediator.Testing
    • Desactiva el caching para tests

Estructura de Generación

Handler decorado:
┌─────────────────────────────┐
│ [GenerateExecutePipeline...] │
│ [GeneratePipelineTestBase]   │ ← Dispara TestGenerator
└─────────────────────────────┘
           │
           ▼
    Testing Generator
           │
   ┌───────┼───────────────┐
   ▼       ▼               ▼
Attr   Interfaces       TestBase
│       │                  │
└───────┴──────────────────┘
         │
         ▼
  {Handler}PipelineTestBase.g.cs
  - IOverrideStep{N}
  - IOverride{Handler}Steps
  - class {Handler}PipelineTestBase
  - shared NoOpHybridCache (single file)

Estructura de la Clase Base

Herencia

public abstract class MyHandlerPipelineTestBase : BaseHandlerResponses

Hereda de BaseHandlerResponses para acceso a métodos idiomáticos de error:

Método Descripción
InvalidStateError(message) Error de estado inválido
NotFoundError(message) Recurso no encontrado
ValidationError(message) Error de validación
ExceptionError(exception) Error de excepción
Next() Continuar
Break() Detener pipeline

Constructor Automático

protected MyHandlerPipelineTestBase()
{
    var services = new ServiceCollection();
    
    // Mediator generado por el assembly target
    services.AddMediator();
    
    // IHybridCache configurado con Eternet.Mediator.Testing.NoOpHybridCache
    ConfigureHybridCache(services);
    
    // Todos los pasos y handlers registrados
    services.AddEternetMediatorStepsServices();
    services.AddEternetMediatorHandlersServices();
    
    // Permitir que la clase derivada personalice servicios
    ConfigureServices(services);
    
    // Construir el contenedor
    var rootProvider = services.BuildServiceProvider();
    _scope = rootProvider.CreateScope();
    _serviceProvider = _scope.ServiceProvider;
    
}

Métodos Principales

ExecutePipelineAsync

Ejecuta solo los pasos, sin behaviors:

protected virtual async Task<MyHandler.Response> ExecutePipelineAsync(
    MyHandler.Request request,
    CancellationToken cancellationToken = default)
{
    GeneratedPipelineRuntime.ScopeLease? runtimeScope = null;
    if (!GeneratedPipelineRuntime.HasCurrentScope)
    {
        runtimeScope = GeneratedPipelineRuntime.EnterScope();
    }
    await using var ambientRuntimeScope = runtimeScope;
    request.StepsResults = new MyHandlerRequestStepsResults();
    GeneratedPipelineRuntime.Publish<MyHandler.Request>(request, nullable: false);
    
    try
    {
        // Ejecuta cada paso secuencialmente
        var result1 = await ExecuteStep1Async(request, ...);
        request.StepsResults.Step1Result = result1;
        
        var result2 = await ExecuteStep2Async(request, result1, ...);
        request.StepsResults.Step2Result = result2;
        
        var result3 = await ExecuteStep3Async(request, result2);
        request.StepsResults.Step3Result = result3;
        
        // Ejecutar el handler
        var handler = GetService<MyHandler>();
        return handler.Handle(request);
    }
    finally
    {
        await GeneratedPipelineRuntime.DisposeCurrentFrameResourcesAsync();
    }
}

Chunk Helpers

When the pipeline response implements IChunkCommandResult or derives from ChunkCommandResult, the generated test base also includes:

  • ExecuteChunkSequenceAsync(...)
  • ExecuteChunkSequenceWithMediatorAsync(...)
  • AssertChunkProgression(...)

These helpers are intended for worker-driven chunk commands where the same request shape is dispatched multiple times and each invocation performs one bounded unit of work.

Example:

var observations = await ExecuteChunkSequenceWithMediatorAsync(
    _ => new ProcessReplicationChunk.Request
    {
        ReplicationId = replicationId,
        ExecutionId = executionId,
        ForceStateless = true
    });

AssertChunkProgression(
    observations,
    new ChunkProgressExpectation(true, "Processing", 2),
    new ChunkProgressExpectation(false, "Completed", 4));

If the command pauses, the helper stops on the paused response so the test can assert the resume path explicitly.

ExecuteWithMediatorAsync

Ejecuta con todos los IPipelineBehavior:

protected virtual async Task<MyHandler.Response> ExecuteWithMediatorAsync(
    MyHandler.Request request,
    CancellationToken cancellationToken = default)
{
    var mediator = GetService<IMediator>();
    return await mediator.Send(request, cancellationToken);
}
Métodos Execute{StepName}Async

Métodos virtuales para cada paso que soportan sobrescritura:

protected virtual async Task<Step1Result> ExecuteStep1Async(
    MyHandler.Request request,
    CancellationToken cancellationToken)
{
    // Prioridad 1: Individual interface
    if (this is IOverrideStep1 individualOverride)
        return await individualOverride.Step1Async(request, cancellationToken);

    // Prioridad 2: All steps interface
    if (this is IOverrideMyHandlerSteps allStepsOverride)
        return await allStepsOverride.Step1Async(request, cancellationToken);

    // Prioridad 3: Real step
    var step = GetService<Step1>();
    return await step.Handle(request, cancellationToken);
}

Métodos de Configuración

ConfigureServices

Personaliza el contenedor de servicios:

protected virtual void ConfigureServices(IServiceCollection services)
{
    // Override en subclases para agregar mocks, configuración, etc.
}

Ejemplo:

public class MyTestsWithMocks : MyHandlerPipelineTestBase
{
    private readonly Mock<IUserRepository> _userRepoMock = new();

    protected override void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(_userRepoMock.Object);
    }
}
ConfigureHybridCache

Personaliza el cache:

protected virtual void ConfigureHybridCache(IServiceCollection services)
{
    services.AddSingleton<IHybridCache, Eternet.Mediator.Testing.NoOpHybridCache>();
}

Ejemplo:

public class MyTestsWithCache : MyHandlerPipelineTestBase
{
    protected override void ConfigureHybridCache(IServiceCollection services)
    {
        services.AddMemoryCache();
        services.AddSingleton<IHybridCache, MemoryHybridCache>();
    }
}

Métodos de Acceso a Servicios

GetService<T>

Obtiene un servicio requerido:

protected T GetService<T>() where T : notnull
{
    return _serviceProvider.GetRequiredService<T>();
}
GetOptionalService<T>

Obtiene un servicio opcional:

protected T? GetOptionalService<T>() where T : class
{
    return _serviceProvider.GetService<T>();
}

Arquitectura

Flujo de Generación

Código Fuente Anotado
┌──────────────────────────────────────┐
│ [GeneratePipelineTestBase]           │
│ public class MyHandler { ... }       │
└──────────────────┬───────────────────┘
                   │
         ┌─────────┴──────────┐
         ▼                    ▼
    Roslyn analiza      Mediator.Generator
    la clase            genera PipelineExecutor
         │                    │
         ▼                    ▼
  Detecta atributo      Testing.Generator
  GeneratePipelineTestBase analiza pipeline
         │
         ▼
    MediatorTestingAttributeGenerator
    Genera [GeneratePipelineTestBase]
         │
         ▼
    MediatorTestingGenerator
    Analiza semánticamente el handler
         │
    ┌────┴─────────────────────┐
    ▼                          ▼
Genera Interfaces          Genera TestBase
IOverrideStep1              MyHandler
IOverrideStep2              PipelineTestBase
IOverrideStep3
IOverrideMyHandlerSteps

Separación de Generadores

┌─────────────────────────────────────────────────────────────┐
│ tu_assembly.dll (Producción)                                │
│                                                             │
│ ✅ PipelineExecutor (desde Mediator.Generator)             │
│ ✅ StepsResults     (desde Mediator.Generator)             │
│ ✅ MyHandler        (tu código)                            │
│                                                             │
│ ❌ PipelineTestBase (NUNCA incluido)                       │
│ ❌ Interfaces Override (NUNCA incluido)                    │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ tu_tests_assembly.dll (Testing)                             │
│                                                             │
│ ✅ PipelineTestBase  (desde Testing.Generator)             │
│ ✅ Interfaces Override (desde Testing.Generator)           │
│ ✅ MyHandlerTests    (tu código de test)                   │
│                                                             │
│ ✅ IHybridCache (solo para testing)                        │
└─────────────────────────────────────────────────────────────┘

Código Compartido

Ambos generadores usan código compartido desde Eternet.Generators.Shared:

  • PipelineSemanticAnalyzer - Análisis semántico del pipeline
  • Helper methods para Roslyn

Esto asegura:

  • ✅ Análisis consistente
  • ✅ Sin duplicación de código
  • ✅ Fácil mantenimiento

Ver También


Licencia

Copyright © Eternet

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

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
3.1.9 101 4/13/2026
3.1.8 91 4/13/2026
3.1.7 95 4/12/2026
3.1.6 83 4/12/2026
3.1.5 108 4/10/2026
3.1.4 107 4/10/2026
3.1.3 121 4/6/2026
3.1.2 87 4/5/2026
3.1.1 79 4/5/2026
3.1.0 99 4/5/2026
3.0.0 87 4/5/2026
2.1.0 90 4/4/2026
2.0.7 90 4/3/2026
2.0.6 122 3/21/2026
2.0.3 92 3/20/2026
2.0.2 94 3/20/2026
2.0.1 95 3/18/2026
1.3.17 142 3/14/2026
1.3.16 134 3/10/2026
1.3.15 92 3/9/2026
Loading failed

Generated pipeline test bases now follow aggregate-root relational update semantics.