diff --git a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs index 4b5da422..63d7ffae 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs @@ -16,7 +16,6 @@ using GFramework.Core.Events; using GFramework.Core.Ioc; using GFramework.Core.Logging; using GFramework.Core.Query; -using GFramework.Cqrs.Abstractions.Cqrs; namespace GFramework.Core.Tests.Architectures; @@ -45,6 +44,15 @@ namespace GFramework.Core.Tests.Architectures; [TestFixture] public class ArchitectureContextTests { + private AsyncQueryExecutor? _asyncQueryBus; + private CommandExecutor? _commandBus; + private MicrosoftDiContainer? _container; + + private ArchitectureContext? _context; + private DefaultEnvironment? _environment; + private EventBus? _eventBus; + private QueryExecutor? _queryBus; + [SetUp] public void SetUp() { @@ -76,14 +84,6 @@ public class ArchitectureContextTests _context = new ArchitectureContext(_container); } - private ArchitectureContext? _context; - private MicrosoftDiContainer? _container; - private EventBus? _eventBus; - private CommandExecutor? _commandBus; - private QueryExecutor? _queryBus; - private AsyncQueryExecutor? _asyncQueryBus; - private DefaultEnvironment? _environment; - /// /// 测试构造函数在所有参数都有效时不应抛出异常 /// @@ -308,8 +308,10 @@ public class ArchitectureContextTests [Test] public async Task SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently() { + const int workerCount = 16; using var startGate = new ManualResetEventSlim(false); using var allowResolutionToComplete = new ManualResetEventSlim(false); + using var workersReady = new CountdownEvent(workerCount); var resolutionCallCount = 0; var runtime = new Mock(MockBehavior.Strict); var container = new Mock(MockBehavior.Strict); @@ -329,14 +331,19 @@ public class ArchitectureContextTests }); var context = new ArchitectureContext(container.Object); - var requests = Enumerable.Range(0, 16) + var requests = Enumerable.Range(0, workerCount) .Select(_ => Task.Run(async () => { + workersReady.Signal(); startGate.Wait(); return await context.SendRequestAsync(new TestCqrsRequest()); })) .ToArray(); + Assert.That( + workersReady.Wait(TimeSpan.FromSeconds(1)), + Is.True, + "Expected all workers to be ready before releasing start gate."); startGate.Set(); Assert.That( @@ -344,8 +351,6 @@ public class ArchitectureContextTests Is.True, "Expected at least one CQRS runtime resolution attempt."); - // 留出一个短暂窗口,让并发首次访问都在 runtime 尚未发布前抵达同一初始化点。 - await Task.Delay(50); allowResolutionToComplete.Set(); var responses = await Task.WhenAll(requests); diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs index 3faedecb..275101d9 100644 --- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs +++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs @@ -1,10 +1,10 @@ using System.Reflection; using GFramework.Core.Abstractions.Bases; +using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Ioc; using GFramework.Core.Logging; using GFramework.Core.Tests.Cqrs; using GFramework.Core.Tests.Systems; -using GFramework.Cqrs.Abstractions.Cqrs; namespace GFramework.Core.Tests.Ioc; @@ -14,6 +14,8 @@ namespace GFramework.Core.Tests.Ioc; [TestFixture] public class MicrosoftDiContainerTests { + private MicrosoftDiContainer _container = null!; + /// /// 在每个测试方法执行前进行设置 /// @@ -33,8 +35,6 @@ public class MicrosoftDiContainerTests CqrsTestRuntime.RegisterInfrastructure(_container); } - private MicrosoftDiContainer _container = null!; - /// /// 测试注册单例实例的功能 /// @@ -151,6 +151,21 @@ public class MicrosoftDiContainerTests Assert.That(result, Is.SameAs(instance)); } + /// + /// 测试当 CQRS 基础设施已手动接线后,再调用处理器注册入口不会重复注册 runtime seam。 + /// + [Test] + public void RegisterHandlers_Should_Not_Duplicate_Cqrs_Infrastructure_When_It_Is_Already_Registered() + { + Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + + CqrsTestRuntime.RegisterHandlers(_container); + + Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + Assert.That(_container.GetAll(), Has.Count.EqualTo(1)); + } + /// /// 测试当没有实例时获取应返回 null 的功能 /// diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs index 50890658..57d4effd 100644 --- a/GFramework.Tests.Common/CqrsTestRuntime.cs +++ b/GFramework.Tests.Common/CqrsTestRuntime.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Ioc; @@ -80,22 +79,31 @@ public static class CqrsTestRuntime /// 为裸测试容器补齐默认 CQRS runtime seam。 /// /// 目标测试容器。 + /// + /// 反射调用底层 CQRS runtime 或注册器构造函数失败时抛出。 /// /// 这使仅使用 的测试环境也能观察与生产路径一致的 runtime 行为, /// 而无需完整启动服务模块管理器。 + /// 该方法按服务类型执行幂等注册,只会补齐当前容器中尚未接线的 CQRS 基础设施。 /// public static void RegisterInfrastructure(MicrosoftDiContainer container) { ArgumentNullException.ThrowIfNull(container); - var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher"); - var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime)); - var runtime = (ICqrsRuntime)CqrsDispatcherConstructor.Invoke([container, runtimeLogger]); - var registrar = - (ICqrsHandlerRegistrar)DefaultCqrsHandlerRegistrarConstructor.Invoke([container, registrarLogger]); + if (container.Get() is null) + { + var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger(CqrsDispatcherType.Name); + var runtime = (ICqrsRuntime)CqrsDispatcherConstructor.Invoke([container, runtimeLogger]); + container.Register(runtime); + } - container.Register(runtime); - container.Register(registrar); + if (container.Get() is null) + { + var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(DefaultCqrsHandlerRegistrarType.Name); + var registrar = + (ICqrsHandlerRegistrar)DefaultCqrsHandlerRegistrarConstructor.Invoke([container, registrarLogger]); + container.Register(registrar); + } } /// @@ -103,6 +111,14 @@ public static class CqrsTestRuntime /// /// 承载处理器映射的测试容器。 /// 要扫描的程序集集合。 + /// + /// 。 + /// + /// 反射调用底层 CQRS 处理器注册入口失败时抛出。 + /// + /// 该入口会自动调用 ,因此测试通常无需预先手动接线 CQRS 基础设施。 + /// 程序集去重与空元素过滤由生产注册入口统一处理,避免测试辅助层复制相同筛选逻辑。 + /// public static void RegisterHandlers(MicrosoftDiContainer container, params Assembly[] assemblies) { ArgumentNullException.ThrowIfNull(container); @@ -113,6 +129,6 @@ public static class CqrsTestRuntime var logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime)); RegisterHandlersMethod.Invoke( null, - [container, assemblies.Where(static assembly => assembly is not null).Distinct().ToArray(), logger]); + [container, assemblies, logger]); } }