Eternet.Mediator.Testing.Generator
3.1.4
Prefix Reserved
See the version list below for details.
dotnet add package Eternet.Mediator.Testing.Generator --version 3.1.4
NuGet\Install-Package Eternet.Mediator.Testing.Generator -Version 3.1.4
<PackageReference Include="Eternet.Mediator.Testing.Generator" Version="3.1.4"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Eternet.Mediator.Testing.Generator" Version="3.1.4" />
<PackageReference Include="Eternet.Mediator.Testing.Generator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Eternet.Mediator.Testing.Generator --version 3.1.4
#r "nuget: Eternet.Mediator.Testing.Generator, 3.1.4"
#:package Eternet.Mediator.Testing.Generator@3.1.4
#addin nuget:?package=Eternet.Mediator.Testing.Generator&version=3.1.4
#tool nuget:?package=Eternet.Mediator.Testing.Generator&version=3.1.4
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
- Instalación
- Uso Básico
- Generación de Código
- Estructura de la Clase Base
- Chunk Helpers
- Arquitectura
- Ver También
Filosofía
- Generador de Producción (
Eternet.Mediator.Generator): GeneraPipelineExecutor,StepsResults, etc. - Generador de Testing (
Eternet.Mediator.Testing.Generator): GeneraPipelineTestBase, 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:
- Un happy path que verifica
StepsResultsy la respuesta final - Uno o dos error paths que verifican propagación de errores
- Para EF Core: estado del
ChangeTrackerantes deSaveChangesAsync
💡 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
ScopedStatesfield. - Test infrastructure now relies on the framework-owned generated runtime helpers instead of the retired public bag API.
- Existing custom tests that manually used
ScopedStatesshould migrate torequest.StepsResultsand, 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:
Interfaces de Override (una por paso + una general):
IOverrideStep1IOverrideStep2IOverrideStep3IOverrideMyHandlerSteps(todos los pasos)
Clase Base Abstracta:
MyHandlerPipelineTestBase- Hereda de
BaseHandlerResponses - Configura automáticamente servicios en el constructor
- Proporciona
ExecutePipelineAsyncyExecuteWithMediatorAsync - Implementa métodos virtuales para cada paso
NoOpHybridCache (shared):
- Implementación no-op de
IHybridCache - Se genera una sola vez en
Eternet.Mediator.Testing - Desactiva el caching para tests
- Implementación no-op de
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
- Eternet.Mediator/README.md - Documentación principal
- Testing Avanzado - Ejemplos de uso
- AGENTS.md - Instrucciones para AI agents
Licencia
Copyright © Eternet
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 |
Generated pipeline test bases now follow aggregate-root relational update semantics.