GFramework/GFramework.Cqrs.Tests/Mediator/MediatorComprehensiveTests.cs
gewuyou 9109eecea9 test(cqrs): 减少 Mediator 综合测试告警
- 优化测试 helper 类型作用域以消除文件名匹配告警

- 补充异步等待 ConfigureAwait(false) 以满足 analyzer 约束

- 调整集合抽象、字符串比较器和异常参数名用法
2026-04-29 08:19:16 +08:00

722 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Command;
using GFramework.Core.Environment;
using GFramework.Core.Events;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Query;
using GFramework.Cqrs.Abstractions.Cqrs;
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
namespace GFramework.Cqrs.Tests.Mediator;
[TestFixture]
public class MediatorComprehensiveTests
{
/// <summary>
/// 测试初始化方法,在每个测试方法执行前运行。
/// 负责初始化日志工厂、依赖注入容器、自有 CQRS 处理器以及各种总线服务。
/// </summary>
[SetUp]
public void SetUp()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer();
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
loggerField?.SetValue(_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(MediatorComprehensiveTests)));
// 注册基础服务Legacy CQRS
_eventBus = new EventBus();
_commandBus = new CommandExecutor();
_queryBus = new QueryExecutor();
_asyncQueryBus = new AsyncQueryExecutor();
_environment = new DefaultEnvironment();
_container.RegisterPlurality(_eventBus);
_container.RegisterPlurality(_commandBus);
_container.RegisterPlurality(_queryBus);
_container.RegisterPlurality(_asyncQueryBus);
_container.RegisterPlurality(_environment);
CqrsTestRuntime.RegisterHandlers(
_container,
typeof(MediatorComprehensiveTests).Assembly,
typeof(ArchitectureContext).Assembly);
_container.Freeze();
_context = new ArchitectureContext(_container);
}
/// <summary>
/// 测试清理方法,在每个测试方法执行后运行。
/// 负责释放容器和上下文资源。
/// </summary>
[TearDown]
public void TearDown()
{
_context = null;
_container = null;
_eventBus = null;
_commandBus = null;
_queryBus = null;
_asyncQueryBus = null;
_environment = null;
}
private AsyncQueryExecutor? _asyncQueryBus;
private CommandExecutor? _commandBus;
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
private DefaultEnvironment? _environment;
private EventBus? _eventBus;
private QueryExecutor? _queryBus;
/// <summary>
/// 测试SendRequestAsync方法在请求有效时返回结果
/// </summary>
[Test]
public async Task SendRequestAsync_Should_ReturnResult_When_Request_IsValid()
{
var testRequest = new TestRequest { Value = 42 };
var result = await _context!.SendRequestAsync(testRequest).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(42));
}
/// <summary>
/// 测试SendRequestAsync方法在请求为null时抛出ArgumentNullException
/// </summary>
[Test]
public void SendRequestAsync_Should_ThrowArgumentNullException_When_Request_IsNull()
{
Assert.ThrowsAsync<ArgumentNullException>(async () =>
await _context!.SendRequestAsync<int>(null!).ConfigureAwait(false));
}
/// <summary>
/// 测试SendRequest方法在请求有效时返回结果
/// </summary>
[Test]
public void SendRequest_Should_ReturnResult_When_Request_IsValid()
{
var testRequest = new TestRequest { Value = 123 };
var result = _context!.SendRequest(testRequest);
Assert.That(result, Is.EqualTo(123));
}
/// <summary>
/// 测试PublishAsync方法在通知有效时发布通知
/// </summary>
[Test]
public async Task PublishAsync_Should_PublishNotification_When_Notification_IsValid()
{
TestNotificationHandler.LastReceivedMessage = null;
var notification = new TestNotification { Message = "test" };
await _context!.PublishAsync(notification).ConfigureAwait(false);
await Task.Delay(100).ConfigureAwait(false);
Assert.That(TestNotificationHandler.LastReceivedMessage, Is.EqualTo("test"));
}
/// <summary>
/// 测试CreateStream方法在流请求有效时返回流
/// </summary>
[Test]
public async Task CreateStream_Should_ReturnStream_When_StreamRequest_IsValid()
{
var testStreamRequest = new TestStreamRequest { Values = [1, 2, 3, 4, 5] };
var stream = _context!.CreateStream(testStreamRequest);
var results = new List<int>();
await foreach (var item in stream.ConfigureAwait(false))
{
results.Add(item);
}
Assert.That(results, Is.EqualTo(new[] { 1, 2, 3, 4, 5 }));
}
/// <summary>
/// 测试SendAsync方法无返回值命令在命令有效时执行
/// </summary>
[Test]
public async Task SendAsync_CommandWithoutResult_Should_Execute_When_Command_IsValid()
{
var testCommand = new TestCommand { ShouldExecute = true };
await _context!.SendAsync(testCommand).ConfigureAwait(false);
Assert.That(testCommand.Executed, Is.True);
}
/// <summary>
/// 测试SendAsync方法带返回值命令在命令有效时返回结果
/// </summary>
[Test]
public async Task SendAsync_CommandWithResult_Should_ReturnResult_When_Command_IsValid()
{
var testCommand = new TestCommandWithResult { ResultValue = 42 };
var result = await _context!.SendAsync(testCommand).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(42));
}
/// <summary>
/// 测试GetService方法使用缓存
/// </summary>
[Test]
public void GetService_Should_Use_Cache()
{
var firstResult = _context!.GetService<IEventBus>();
Assert.That(firstResult, Is.Not.Null);
Assert.That(firstResult, Is.SameAs(_eventBus));
var secondResult = _context.GetService<IEventBus>();
Assert.That(secondResult, Is.SameAs(firstResult));
}
/// <summary>
/// 测试未注册的 CQRS handler 时抛出 InvalidOperationException
/// </summary>
[Test]
public void Unregistered_Cqrs_Handler_Should_Throw_InvalidOperationException()
{
var containerWithoutHandlers = new MicrosoftDiContainer();
containerWithoutHandlers.Freeze();
var contextWithoutHandlers = new ArchitectureContext(containerWithoutHandlers);
var testRequest = new TestRequest { Value = 42 };
Assert.ThrowsAsync<InvalidOperationException>(async () =>
await contextWithoutHandlers.SendRequestAsync(testRequest).ConfigureAwait(false));
}
/// <summary>
/// 测试多个通知处理器都被调用
/// </summary>
[Test]
public async Task Multiple_Notification_Handlers_Should_All_Be_Invoked()
{
// 重置静态字段
TestNotificationHandler.LastReceivedMessage = null;
TestNotificationHandler2.LastReceivedMessage = null;
TestNotificationHandler3.LastReceivedMessage = null;
var notification = new TestNotification { Message = "multi-handler test" };
await _context!.PublishAsync(notification).ConfigureAwait(false);
await Task.Delay(100).ConfigureAwait(false);
// 验证所有处理器都被调用
Assert.That(TestNotificationHandler.LastReceivedMessage, Is.EqualTo("multi-handler test"));
Assert.That(TestNotificationHandler2.LastReceivedMessage, Is.EqualTo("multi-handler test"));
Assert.That(TestNotificationHandler3.LastReceivedMessage, Is.EqualTo("multi-handler test"));
}
/// <summary>
/// 测试CancellationToken取消长时间运行的请求
/// </summary>
[Test]
public async Task CancellationToken_Should_Cancel_Long_Running_Request()
{
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
var longRequest = new TestLongRunningRequest { DelayMs = 1000 };
// 应该在50ms后被取消
Assert.ThrowsAsync<TaskCanceledException>(async () =>
await _context!.SendRequestAsync(longRequest, cts.Token).ConfigureAwait(false));
}
/// <summary>
/// 测试CancellationToken取消流请求
/// </summary>
[Test]
public async Task CancellationToken_Should_Cancel_Stream_Request()
{
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
var longStreamRequest = new TestLongStreamRequest { ItemCount = 1000 };
var stream = _context!.CreateStream(longStreamRequest, cts.Token);
var results = new List<int>();
// 流应该在100ms后被取消TaskCanceledException 继承自 OperationCanceledException
Assert.CatchAsync<OperationCanceledException>(async () =>
{
await foreach (var item in stream.ConfigureAwait(false))
{
results.Add(item);
}
});
// 验证只处理了部分数据
Assert.That(results.Count, Is.LessThan(1000));
}
/// <summary>
/// 测试并发 CQRS 请求不会相互干扰
/// </summary>
[Test]
public async Task Concurrent_Cqrs_Requests_Should_Not_Interfere()
{
const int requestCount = 10;
var tasks = new List<Task<int>>();
// 并发发送多个请求
for (int i = 0; i < requestCount; i++)
{
var request = new TestRequest { Value = i };
tasks.Add(_context!.SendRequestAsync(request).AsTask());
}
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
// 验证所有结果都正确返回
Assert.That(results.Length, Is.EqualTo(requestCount));
Assert.That(results.OrderBy(x => x), Is.EqualTo(Enumerable.Range(0, requestCount)));
}
/// <summary>
/// 测试处理器异常被正确传播
/// </summary>
[Test]
public async Task Handler_Exception_Should_Be_Propagated()
{
var faultyRequest = new TestFaultyRequest();
Assert.ThrowsAsync<InvalidOperationException>(async () =>
await _context!.SendRequestAsync(faultyRequest).ConfigureAwait(false));
}
/// <summary>
/// 测试多个命令处理器可以修改同一对象
/// </summary>
[Test]
public async Task Multiple_Command_Handlers_Can_Modify_Same_Object()
{
var sharedData = new SharedData();
var command1 = new TestModifyDataCommand { Data = sharedData, Value = 10 };
var command2 = new TestModifyDataCommand { Data = sharedData, Value = 20 };
await _context!.SendAsync(command1).ConfigureAwait(false);
await _context.SendAsync(command2).ConfigureAwait(false);
// 验证数据被正确修改
Assert.That(sharedData.Value, Is.EqualTo(30)); // 10 + 20
}
/// <summary>
/// 测试通知顺序被保留
/// </summary>
[Test]
public async Task Notification_Ordering_Should_Be_Preserved()
{
var receivedOrder = new List<string>();
TestOrderedNotificationHandler.ReceivedMessages = receivedOrder;
var notifications = new[]
{
new TestOrderedNotification { Order = 1, Message = "First" },
new TestOrderedNotification { Order = 2, Message = "Second" },
new TestOrderedNotification { Order = 3, Message = "Third" }
};
foreach (var notification in notifications)
{
await _context!.PublishAsync(notification).ConfigureAwait(false);
}
await Task.Delay(200).ConfigureAwait(false); // 等待所有处理完成
// 验证接收顺序与发送顺序一致
Assert.That(receivedOrder.Count, Is.EqualTo(3));
Assert.That(receivedOrder[0], Is.EqualTo("First"));
Assert.That(receivedOrder[1], Is.EqualTo("Second"));
Assert.That(receivedOrder[2], Is.EqualTo("Third"));
}
/// <summary>
/// 测试流请求带过滤功能
/// </summary>
[Test]
public async Task Stream_Request_With_Filtering()
{
var filterRequest = new TestFilterStreamRequest
{
Values = Enumerable.Range(1, 10).ToArray(),
FilterEven = true
};
var stream = _context!.CreateStream(filterRequest);
var results = new List<int>();
await foreach (var item in stream.ConfigureAwait(false))
{
results.Add(item);
}
// 验证只返回偶数
Assert.That(results.All(x => x % 2 == 0), Is.True);
Assert.That(results, Is.EqualTo(new[] { 2, 4, 6, 8, 10 }));
}
/// <summary>
/// 测试请求验证使用Behaviors
/// </summary>
[Test]
public async Task Request_Validation_With_Behaviors()
{
var invalidCommand = new TestValidatedCommand { Name = "" }; // 无效:空字符串
Assert.ThrowsAsync<ArgumentException>(async () =>
await _context!.SendAsync(invalidCommand).ConfigureAwait(false));
}
/// <summary>
/// 测试 CQRS 性能基准
/// </summary>
[Test]
public async Task Performance_Benchmark_For_Cqrs()
{
const int iterations = 1000;
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
var request = new TestRequest { Value = i };
var result = await _context!.SendRequestAsync(request).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(i));
}
stopwatch.Stop();
var avgTime = stopwatch.ElapsedMilliseconds / (double)iterations;
// 验证性能在合理范围内平均每个请求不超过10ms
Assert.That(avgTime, Is.LessThan(10.0));
Console.WriteLine($"Average time per request: {avgTime:F2}ms");
}
/// <summary>
/// 测试自有 CQRS 和传统 CQRS 可以共存
/// </summary>
[Test]
public async Task Cqrs_And_Legacy_CQRS_Can_Coexist()
{
// 使用传统方式
var legacyCommand = new TestLegacyCommand();
_context!.SendCommand(legacyCommand);
Assert.That(legacyCommand.Executed, Is.True);
// 使用自有 CQRS 方式
var mediatorCommand = new TestCommandWithResult { ResultValue = 999 };
var result = await _context.SendAsync(mediatorCommand).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(999));
// 验证两者可以同时工作
Assert.That(legacyCommand.Executed, Is.True);
Assert.That(result, Is.EqualTo(999));
}
#region Advanced Test Classes for CQRS Features
public sealed record TestLongRunningRequest : IRequest<string>
{
public int DelayMs { get; init; }
}
public sealed class TestLongRunningRequestHandler : IRequestHandler<TestLongRunningRequest, string>
{
public async ValueTask<string> Handle(TestLongRunningRequest request, CancellationToken cancellationToken)
{
await Task.Delay(request.DelayMs, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
return "Completed";
}
}
public sealed record TestLongStreamRequest : IStreamRequest<int>
{
public int ItemCount { get; init; }
}
public sealed class TestLongStreamRequestHandler : IStreamRequestHandler<TestLongStreamRequest, int>
{
public async IAsyncEnumerable<int> Handle(
TestLongStreamRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
for (int i = 0; i < request.ItemCount; i++)
{
cancellationToken.ThrowIfCancellationRequested();
yield return i;
await Task.Delay(10, cancellationToken).ConfigureAwait(false); // 模拟处理延迟
}
}
}
public sealed record TestFaultyRequest : IRequest<string>;
public sealed class TestFaultyRequestHandler : IRequestHandler<TestFaultyRequest, string>
{
public ValueTask<string> Handle(TestFaultyRequest request, CancellationToken cancellationToken)
{
throw new InvalidOperationException("Handler failed intentionally");
}
}
public class SharedData
{
public int Value { get; set; }
}
public sealed record TestModifyDataCommand : IRequest<Unit>
{
public SharedData Data { get; init; } = null!;
public int Value { get; init; }
}
public sealed class TestModifyDataCommandHandler : IRequestHandler<TestModifyDataCommand, Unit>
{
public ValueTask<Unit> Handle(TestModifyDataCommand request, CancellationToken cancellationToken)
{
request.Data.Value += request.Value;
return ValueTask.FromResult(Unit.Value);
}
}
public sealed record TestCachingQuery : IRequest<string>
{
public string Key { get; init; } = string.Empty;
public IDictionary<string, string> Cache { get; init; } = new Dictionary<string, string>(StringComparer.Ordinal);
}
public sealed class TestCachingQueryHandler : IRequestHandler<TestCachingQuery, string>
{
public ValueTask<string> Handle(TestCachingQuery request, CancellationToken cancellationToken)
{
if (request.Cache.TryGetValue(request.Key, out var cachedValue))
{
return new ValueTask<string>(cachedValue);
}
var newValue = $"Value_for_{request.Key}";
request.Cache[request.Key] = newValue;
return new ValueTask<string>(newValue);
}
}
public sealed record TestOrderedNotification : INotification
{
public int Order { get; init; }
public string Message { get; init; } = string.Empty;
}
public sealed class TestOrderedNotificationHandler : INotificationHandler<TestOrderedNotification>
{
public static ICollection<string> ReceivedMessages { get; set; } = new List<string>();
public ValueTask Handle(TestOrderedNotification notification, CancellationToken cancellationToken)
{
ReceivedMessages.Add(notification.Message);
return ValueTask.CompletedTask;
}
}
// 额外的通知处理器来测试多处理器场景
public sealed class TestNotificationHandler2 : INotificationHandler<TestNotification>
{
public static string? LastReceivedMessage { get; set; }
public ValueTask Handle(TestNotification notification, CancellationToken cancellationToken)
{
LastReceivedMessage = notification.Message;
return ValueTask.CompletedTask;
}
}
public sealed class TestNotificationHandler3 : INotificationHandler<TestNotification>
{
public static string? LastReceivedMessage { get; set; }
public ValueTask Handle(TestNotification notification, CancellationToken cancellationToken)
{
LastReceivedMessage = notification.Message;
return ValueTask.CompletedTask;
}
}
public sealed record TestFilterStreamRequest : IStreamRequest<int>
{
public int[] Values { get; init; } = [];
public bool FilterEven { get; init; }
}
public sealed class TestFilterStreamRequestHandler : IStreamRequestHandler<TestFilterStreamRequest, int>
{
public async IAsyncEnumerable<int> Handle(
TestFilterStreamRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
foreach (var value in request.Values)
{
cancellationToken.ThrowIfCancellationRequested();
if (request.FilterEven && value % 2 != 0)
continue;
yield return value;
await Task.Yield();
}
}
}
public sealed record TestValidatedCommand : IRequest<Unit>
{
public string Name { get; init; } = string.Empty;
}
public sealed class TestValidatedCommandHandler : IRequestHandler<TestValidatedCommand, Unit>
{
public ValueTask<Unit> Handle(TestValidatedCommand request, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(request.Name))
{
throw new ArgumentException("Name cannot be empty.", nameof(request));
}
return ValueTask.FromResult(Unit.Value);
}
}
// 传统命令用于共存测试
public class TestLegacyCommand : ICommand
{
public bool Executed { get; private set; }
public void Execute()
{
Executed = true;
}
public void SetContext(IArchitectureContext context)
{
// 不需要实现
}
public IArchitectureContext GetContext()
{
return null!;
}
}
#endregion
#region Test Classes - CQRS Runtime
// ✅ 这些类使用自有 CQRS IRequest
public sealed record TestRequest : IRequest<int>
{
public int Value { get; init; }
}
public sealed record TestCommand : IRequest<Unit>
{
public bool ShouldExecute { get; init; }
public bool Executed { get; set; }
}
public sealed record TestCommandWithResult : IRequest<int>
{
public int ResultValue { get; init; }
}
public sealed record TestQuery : IRequest<string>
{
public string QueryResult { get; init; } = string.Empty;
}
public sealed record TestNotification : INotification
{
public string Message { get; init; } = string.Empty;
}
public sealed record TestStreamRequest : IStreamRequest<int>
{
public int[] Values { get; init; } = [];
}
// ✅ 这些 Handler 使用自有 CQRS IRequestHandler
public sealed class TestRequestHandler : IRequestHandler<TestRequest, int>
{
public ValueTask<int> Handle(TestRequest request, CancellationToken cancellationToken)
{
return new ValueTask<int>(request.Value);
}
}
public sealed class TestCommandHandler : IRequestHandler<TestCommand, Unit>
{
public ValueTask<Unit> Handle(TestCommand request, CancellationToken cancellationToken)
{
if (request.ShouldExecute)
{
request.Executed = true;
}
return ValueTask.FromResult(Unit.Value);
}
}
public sealed class TestCommandWithResultHandler : IRequestHandler<TestCommandWithResult, int>
{
public ValueTask<int> Handle(TestCommandWithResult request, CancellationToken cancellationToken)
{
return new ValueTask<int>(request.ResultValue);
}
}
public sealed class TestQueryHandler : IRequestHandler<TestQuery, string>
{
public ValueTask<string> Handle(TestQuery request, CancellationToken cancellationToken)
{
return new ValueTask<string>(request.QueryResult);
}
}
public sealed class TestNotificationHandler : INotificationHandler<TestNotification>
{
public static string? LastReceivedMessage { get; set; }
public ValueTask Handle(TestNotification notification, CancellationToken cancellationToken)
{
LastReceivedMessage = notification.Message;
return ValueTask.CompletedTask;
}
}
public sealed class TestStreamRequestHandler : IStreamRequestHandler<TestStreamRequest, int>
{
public async IAsyncEnumerable<int> Handle(
TestStreamRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
foreach (var value in request.Values)
{
cancellationToken.ThrowIfCancellationRequested();
yield return value;
await Task.Yield();
}
}
}
#endregion
}