diff --git a/GFramework.Core.Tests/GFramework.Core.Tests.csproj b/GFramework.Core.Tests/GFramework.Core.Tests.csproj index 86171dc..af6aebe 100644 --- a/GFramework.Core.Tests/GFramework.Core.Tests.csproj +++ b/GFramework.Core.Tests/GFramework.Core.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/GFramework.Core.Tests/coroutine/CommandCoroutineExtensionsTests.cs b/GFramework.Core.Tests/coroutine/CommandCoroutineExtensionsTests.cs new file mode 100644 index 0000000..408b83a --- /dev/null +++ b/GFramework.Core.Tests/coroutine/CommandCoroutineExtensionsTests.cs @@ -0,0 +1,429 @@ +using GFramework.Core.Abstractions.architecture; +using GFramework.Core.Abstractions.command; +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.Abstractions.events; +using GFramework.Core.Abstractions.rule; +using GFramework.Core.coroutine.extensions; +using GFramework.Core.coroutine.instructions; +using Moq; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// CommandCoroutineExtensions的单元测试类 +/// 测试内容包括: +/// - SendCommandCoroutineWithErrorHandler扩展方法 +/// - SendCommandAndWaitEventCoroutine扩展方法 +/// +[TestFixture] +public class CommandCoroutineExtensionsTests +{ + /// + /// 测试用的简单命令类 + /// + private class TestCommand : IAsyncCommand + { + private IArchitectureContext? _context; + public void SetContext(IArchitectureContext context) => _context = context; + public IArchitectureContext GetContext() => _context!; + + public Task ExecuteAsync() + { + return Task.CompletedTask; + } + } + + /// + /// 测试用的简单事件类 + /// + private class TestEvent + { + public string Data { get; set; } = string.Empty; + } + + /// + /// 上下文感知基类的模拟实现 + /// + private class TestContextAware : IContextAware + { + public Mock _mockContext = new(); + + public IArchitectureContext GetContext() + { + return _mockContext.Object; + } + + public void SetContext(IArchitectureContext context) + { + } + } + + /// + /// 验证SendCommandCoroutineWithErrorHandler应该能正常执行成功的命令 + /// + [Test] + public async Task SendCommandCoroutineWithErrorHandler_Should_Execute_Successful_Command() + { + var command = new TestCommand(); + Exception? capturedException = null; + var contextAware = new TestContextAware(); + + // 设置上下文发送命令的模拟行为 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command, ex => capturedException = ex); + + // 迭代协程直到完成 + while (coroutine.MoveNext()) + { + if (coroutine.Current is WaitForTask) + { + // 等待任务完成 + await Task.Delay(10); + } + } + + Assert.That(capturedException, Is.Null); + } + + /// + /// 验证SendCommandCoroutineWithErrorHandler应该捕获命令执行中的异常 + /// + [Test] + public async Task SendCommandCoroutineWithErrorHandler_Should_Capture_Command_Exception() + { + var command = new TestCommand(); + Exception? capturedException = null; + var contextAware = new TestContextAware(); + var expectedException = new InvalidOperationException("Test exception"); + + // 设置上下文发送命令的模拟行为,返回失败的任务 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.FromException(expectedException)); + + var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command, ex => capturedException = ex); + + // 迭代协程直到完成 + while (coroutine.MoveNext()) + { + if (coroutine.Current is WaitForTask) + { + // 等待任务完成 + await Task.Delay(10); + } + } + + Assert.That(capturedException, Is.Not.Null); + // 异常被包装为 AggregateException + Assert.That(capturedException, Is.TypeOf()); + var aggregateException = (AggregateException)capturedException!; + Assert.That(aggregateException.InnerException, Is.EqualTo(expectedException)); + } + + /// + /// 验证SendCommandCoroutineWithErrorHandler应该处理null错误回调 + /// + [Test] + public async Task SendCommandCoroutineWithErrorHandler_Should_Handle_Null_Error_Handler() + { + var command = new TestCommand(); + var contextAware = new TestContextAware(); + var expectedException = new InvalidOperationException("Test exception"); + + // 设置上下文发送命令的模拟行为,返回失败的任务 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.FromException(expectedException)); + + // 使用null作为错误处理程序 + var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command, null); + + // 迭代协程直到完成 + while (coroutine.MoveNext()) + { + if (coroutine.Current is WaitForTask) + { + // 等待任务完成 + await Task.Delay(10); + } + } + + // 应该不会抛出异常 + Assert.Pass(); + } + + /// + /// 验证SendCommandAndWaitEventCoroutine应该在事件触发后完成 + /// + [Test] + public async Task SendCommandAndWaitEventCoroutine_Should_Complete_After_Event_Triggered() + { + var command = new TestCommand(); + var contextAware = new TestContextAware(); + TestEvent? receivedEvent = null; + var eventTriggered = false; + + // 创建事件总线模拟 + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + Action? eventCallback = null; + eventBusMock.Setup(bus => bus.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(callback => eventCallback = callback); + + // 设置上下文服务以返回事件总线 + contextAware._mockContext + .Setup(ctx => ctx.GetService()) + .Returns(eventBusMock.Object); + + // 设置命令执行返回完成的任务 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + var coroutine = contextAware.SendCommandAndWaitEventCoroutine( + command, + ev => + { + receivedEvent = ev; + eventTriggered = true; + }); + + // 启动协程并等待命令执行完成 + coroutine.MoveNext(); // 进入命令发送阶段 + if (coroutine.Current is WaitForTask) + { + await Task.Delay(10); // 等待命令任务完成 + } + + // 此时协程应该在等待事件 + Assert.That(coroutine.MoveNext(), Is.True); // 等待事件阶段 + + // 触发事件 + var testEvent = new TestEvent { Data = "TestData" }; + eventCallback?.Invoke(testEvent); + + // 现在协程应该完成 + Assert.That(coroutine.MoveNext(), Is.False); + + Assert.That(eventTriggered, Is.True); + Assert.That(receivedEvent, Is.Not.Null); + Assert.That(receivedEvent?.Data, Is.EqualTo("TestData")); + } + + /// + /// 验证SendCommandAndWaitEventCoroutine应该处理超时情况 + /// + [Test] + public void SendCommandAndWaitEventCoroutine_Should_Throw_Timeout_Exception() + { + var command = new TestCommand(); + var contextAware = new TestContextAware(); + + // 创建事件总线模拟 + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(bus => bus.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + // 设置上下文服务以返回事件总线 + contextAware._mockContext + .Setup(ctx => ctx.GetService()) + .Returns(eventBusMock.Object); + + // 设置命令执行返回完成的任务 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + // 对于超时情况,我们期望抛出TimeoutException + Assert.Throws(() => + { + var coroutine = contextAware.SendCommandAndWaitEventCoroutine( + command, + null, + 0.1f); // 0.1秒超时 + + // 运行协程直到完成 + while (coroutine.MoveNext()) + { + if (coroutine.Current is WaitForEventWithTimeout timeoutWait) + { + // 模拟超时 + timeoutWait.Update(0.2); // 更新时间超过超时限制 + } + } + }); + } + + /// + /// 验证SendCommandAndWaitEventCoroutine应该处理null事件回调 + /// + [Test] + public async Task SendCommandAndWaitEventCoroutine_Should_Handle_Null_Event_Callback() + { + var command = new TestCommand(); + var contextAware = new TestContextAware(); + + // 创建事件总线模拟 + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + Action? eventCallback = null; + eventBusMock.Setup(bus => bus.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(callback => eventCallback = callback); + + // 设置上下文服务以返回事件总线 + contextAware._mockContext + .Setup(ctx => ctx.GetService()) + .Returns(eventBusMock.Object); + + // 设置命令执行返回完成的任务 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + // 使用null作为事件回调 + var coroutine = contextAware.SendCommandAndWaitEventCoroutine( + command, + null); // null回调 + + // 启动协程 + coroutine.MoveNext(); // 进入命令发送阶段 + if (coroutine.Current is WaitForTask) + { + await Task.Delay(10); // 等待命令任务完成 + } + + // 触发事件 + var testEvent = new TestEvent { Data = "TestData" }; + eventCallback?.Invoke(testEvent); + + // 协程应该能正常完成 + Assert.That(() => coroutine.MoveNext(), Throws.Nothing); + } + + /// + /// 验证SendCommandAndWaitEventCoroutine应该处理命令执行异常 + /// + [Test] + public async Task SendCommandAndWaitEventCoroutine_Should_Handle_Command_Exception() + { + var command = new TestCommand(); + var contextAware = new TestContextAware(); + var expectedException = new InvalidOperationException("Command execution failed"); + + // 创建事件总线模拟 + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(bus => bus.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + // 设置上下文服务以返回事件总线 + contextAware._mockContext + .Setup(ctx => ctx.GetService()) + .Returns(eventBusMock.Object); + + // 设置命令执行返回失败的任务 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.FromException(expectedException)); + + var coroutine = contextAware.SendCommandAndWaitEventCoroutine( + command, + _ => { }); + + // 启动协程 - 命令失败时协程仍然继续 + coroutine.MoveNext(); // 进入命令发送阶段 + if (coroutine.Current is WaitForTask) + { + await Task.Delay(10); // 等待命令任务完成 + } + + // 命令执行失败后,协程继续执行 + Assert.Pass(); + } + + /// + /// 验证SendCommandCoroutineWithErrorHandler应该返回IEnumerator + /// + [Test] + public void SendCommandCoroutineWithErrorHandler_Should_Return_IEnumerator_Of_YieldInstruction() + { + var command = new TestCommand(); + var contextAware = new TestContextAware(); + + // 设置上下文发送命令的模拟行为 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command, ex => { }); + + Assert.That(coroutine, Is.InstanceOf>()); + } + + /// + /// 验证SendCommandAndWaitEventCoroutine应该返回IEnumerator + /// + [Test] + public void SendCommandAndWaitEventCoroutine_Should_Return_IEnumerator_Of_YieldInstruction() + { + var command = new TestCommand(); + var contextAware = new TestContextAware(); + + // 创建事件总线模拟 + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(bus => bus.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + // 设置上下文服务以返回事件总线 + contextAware._mockContext + .Setup(ctx => ctx.GetService()) + .Returns(eventBusMock.Object); + + // 设置命令执行返回完成的任务 + contextAware._mockContext + .Setup(ctx => ctx.SendCommandAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + var coroutine = contextAware.SendCommandAndWaitEventCoroutine( + command, + ev => { }); + + Assert.That(coroutine, Is.InstanceOf>()); + } + + /// + /// 验证SendCommandAndWaitEventCoroutine应该在事件总线为null时处理异常 + /// + [Test] + public void SendCommandAndWaitEventCoroutine_Should_Handle_Null_EventBus() + { + var command = new TestCommand(); + var contextAware = new TestContextAware(); + + // 设置上下文服务以返回null事件总线 + contextAware._mockContext + .Setup(ctx => ctx.GetService()) + .Returns((IEventBus?)null); + + // 创建协程 + var coroutine = contextAware.SendCommandAndWaitEventCoroutine( + command, + ev => { }); + + // 调用 MoveNext 时应该抛出异常,因为事件总线为null + Assert.Throws(() => coroutine.MoveNext()); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/QueryCoroutineExtensionsTests.cs b/GFramework.Core.Tests/coroutine/QueryCoroutineExtensionsTests.cs new file mode 100644 index 0000000..422d175 --- /dev/null +++ b/GFramework.Core.Tests/coroutine/QueryCoroutineExtensionsTests.cs @@ -0,0 +1,356 @@ +using GFramework.Core.Abstractions.architecture; +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.Abstractions.query; +using GFramework.Core.Abstractions.rule; +using GFramework.Core.coroutine.extensions; +using Moq; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// QueryCoroutineExtensions的单元测试类 +/// 测试内容包括: +/// - SendQueryCoroutine扩展方法 +/// +[TestFixture] +public class QueryCoroutineExtensionsTests +{ + /// + /// 测试用的简单查询类 + /// + private class TestQuery : IQuery + { + private IArchitectureContext? _context; + public string QueryData { get; set; } = string.Empty; + public void SetContext(IArchitectureContext context) => _context = context; + public IArchitectureContext GetContext() => _context!; + public string Do() => QueryData; + } + + /// + /// 上下文感知基类的模拟实现 + /// + private class TestContextAware : IContextAware + { + public Mock _mockContext = new Mock(); + + public IArchitectureContext GetContext() + { + return _mockContext.Object; + } + + public void SetContext(IArchitectureContext context) + { + } + } + + /// + /// 验证SendQueryCoroutine应该能正常执行查询并返回结果 + /// + [Test] + public void SendQueryCoroutine_Should_Execute_Query_And_Return_Result() + { + var query = new TestQuery { QueryData = "TestQueryData" }; + string? receivedResult = null; + var resultReceived = false; + var contextAware = new TestContextAware(); + + // 设置上下文发送查询的模拟行为 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns("TestResult"); + + var coroutine = contextAware.SendQueryCoroutine(query, result => + { + receivedResult = result; + resultReceived = true; + }); + + // 迭代协程直到完成 + var moved = coroutine.MoveNext(); + + // SendQueryCoroutine立即执行并返回,所以MoveNext应该返回false + Assert.That(moved, Is.False); + Assert.That(resultReceived, Is.True); + Assert.That(receivedResult, Is.EqualTo("TestResult")); + } + + /// + /// 验证SendQueryCoroutine应该处理不同类型的查询和结果 + /// + [Test] + public void SendQueryCoroutine_Should_Handle_Different_Query_And_Result_Types() + { + // 使用整数查询和布尔结果 + var query = new IntQuery { Value = 42 }; + bool? receivedResult = null; + var resultReceived = false; + var contextAware = new TestContextAware(); + + // 设置上下文发送查询的模拟行为 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns(true); + + var coroutine = contextAware.SendQueryCoroutine(query, result => + { + receivedResult = result; + resultReceived = true; + }); + + // 迭代协程直到完成 + var moved = coroutine.MoveNext(); + + Assert.That(moved, Is.False); + Assert.That(resultReceived, Is.True); + Assert.That(receivedResult, Is.True); + } + + /// + /// 验证SendQueryCoroutine在null回调时应该抛出异常 + /// + [Test] + public void SendQueryCoroutine_Should_Throw_When_Null_Result_Callback() + { + var query = new TestQuery { QueryData = "TestQueryData" }; + var contextAware = new TestContextAware(); + + // 设置上下文发送查询的模拟行为 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns("TestResult"); + + // 使用null作为结果回调,应该抛出NullReferenceException + var coroutine = contextAware.SendQueryCoroutine(query, null!); + + // 迭代协程时应该抛出异常 + Assert.That(() => coroutine.MoveNext(), Throws.TypeOf()); + } + + /// + /// 验证SendQueryCoroutine应该在查询执行期间调用结果处理回调 + /// + [Test] + public void SendQueryCoroutine_Should_Call_Result_Callback_During_Execution() + { + var query = new TestQuery { QueryData = "TestQueryData" }; + string? receivedResult = null; + var callCount = 0; + var contextAware = new TestContextAware(); + + // 设置上下文发送查询的模拟行为 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns("ProcessedResult"); + + var coroutine = contextAware.SendQueryCoroutine(query, result => + { + receivedResult = result; + callCount++; + }); + + // 协程应立即执行查询并调用回调 + coroutine.MoveNext(); + + Assert.That(callCount, Is.EqualTo(1)); + Assert.That(receivedResult, Is.EqualTo("ProcessedResult")); + } + + /// + /// 验证SendQueryCoroutine应该返回IEnumerator + /// + [Test] + public void SendQueryCoroutine_Should_Return_IEnumerator_Of_YieldInstruction() + { + var query = new TestQuery { QueryData = "TestQueryData" }; + var contextAware = new TestContextAware(); + + // 设置上下文发送查询的模拟行为 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns("TestResult"); + + var coroutine = contextAware.SendQueryCoroutine(query, result => { }); + + Assert.That(coroutine, Is.InstanceOf>()); + } + + /// + /// 验证SendQueryCoroutine应该在查询抛出异常时处理异常 + /// + [Test] + public void SendQueryCoroutine_Should_Handle_Query_Exception() + { + var query = new TestQuery { QueryData = "TestQueryData" }; + string? receivedResult = null; + var contextAware = new TestContextAware(); + var expectedException = new InvalidOperationException("Query execution failed"); + + // 设置上下文发送查询的模拟行为,让它抛出异常 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Throws(expectedException); + + // 由于SendQueryCoroutine会直接执行查询,这可能导致异常 + Assert.Throws(() => + { + var coroutine = + contextAware.SendQueryCoroutine(query, result => { receivedResult = result; }); + + // 尝试移动协程,这应该会执行查询并抛出异常 + coroutine.MoveNext(); + }); + } + + /// + /// 验证SendQueryCoroutine应该正确传递查询参数 + /// + [Test] + public void SendQueryCoroutine_Should_Pass_Query_Parameters_Correctly() + { + var query = new TestQuery { QueryData = "PassedQueryData" }; + string? receivedResult = null; + var contextAware = new TestContextAware(); + TestQuery? capturedQuery = null; + + // 设置上下文发送查询的模拟行为,并捕获传入的查询参数 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns((IQuery q) => + { + capturedQuery = (TestQuery)q; + return $"Processed_{capturedQuery.QueryData}"; + }); + + var coroutine = + contextAware.SendQueryCoroutine(query, result => { receivedResult = result; }); + + coroutine.MoveNext(); + + Assert.That(capturedQuery, Is.Not.Null); + Assert.That(capturedQuery!.QueryData, Is.EqualTo("PassedQueryData")); + Assert.That(receivedResult, Is.EqualTo("Processed_PassedQueryData")); + } + + /// + /// 验证SendQueryCoroutine应该处理复杂对象查询 + /// + [Test] + public void SendQueryCoroutine_Should_Handle_Complex_Object_Query() + { + var query = new ComplexQuery + { + Name = "ComplexName", + Values = new List { 1, 2, 3 }, + Metadata = new Dictionary { { "key", "value" } } + }; + + ComplexResult? receivedResult = null; + var contextAware = new TestContextAware(); + + var expectedResult = new ComplexResult + { + ProcessedName = "Processed_ComplexName", + Sum = 6, + Count = 3 + }; + + // 设置上下文发送查询的模拟行为 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns(expectedResult); + + var coroutine = + contextAware.SendQueryCoroutine(query, result => { receivedResult = result; }); + + coroutine.MoveNext(); + + Assert.That(receivedResult, Is.Not.Null); + Assert.That(receivedResult!.ProcessedName, Is.EqualTo("Processed_ComplexName")); + Assert.That(receivedResult.Sum, Is.EqualTo(6)); + Assert.That(receivedResult.Count, Is.EqualTo(3)); + } + + /// + /// 验证SendQueryCoroutine应该处理空字符串结果 + /// + [Test] + public void SendQueryCoroutine_Should_Handle_Empty_String_Result() + { + var query = new TestQuery { QueryData = "TestQueryData" }; + string? receivedResult = null; + var contextAware = new TestContextAware(); + + // 设置上下文发送查询的模拟行为,返回空字符串 + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns(string.Empty); + + var coroutine = + contextAware.SendQueryCoroutine(query, result => { receivedResult = result; }); + + coroutine.MoveNext(); + + Assert.That(receivedResult, Is.EqualTo(string.Empty)); + } + + /// + /// 验证SendQueryCoroutine应该处理null结果 + /// + [Test] + public void SendQueryCoroutine_Should_Handle_Null_Result() + { + var query = new TestQuery { QueryData = "TestQueryData" }; + string? receivedResult = "initial"; + var contextAware = new TestContextAware(); + + // 设置上下文发送查询的模拟行为,返回null + contextAware._mockContext + .Setup(ctx => ctx.SendQuery(It.IsAny())) + .Returns((string)null!); + + var coroutine = + contextAware.SendQueryCoroutine(query, result => { receivedResult = result; }); + + coroutine.MoveNext(); + + Assert.That(receivedResult, Is.Null); + } +} + +/// +/// 用于测试的整数查询类 +/// +internal class IntQuery : IQuery +{ + private IArchitectureContext? _context; + public int Value { get; set; } + public void SetContext(IArchitectureContext context) => _context = context; + public IArchitectureContext GetContext() => _context!; + public bool Do() => Value > 0; +} + +/// +/// 用于测试的复杂查询类 +/// +internal class ComplexQuery : IQuery +{ + private IArchitectureContext? _context; + public string Name { get; set; } = string.Empty; + public List Values { get; set; } = new List(); + public Dictionary Metadata { get; set; } = new Dictionary(); + public void SetContext(IArchitectureContext context) => _context = context; + public IArchitectureContext GetContext() => _context!; + public ComplexResult Do() => new ComplexResult { ProcessedName = Name, Sum = Values.Sum(), Count = Values.Count }; +} + +/// +/// 用于测试的复杂结果类 +/// +internal class ComplexResult +{ + public string ProcessedName { get; set; } = string.Empty; + public int Sum { get; set; } + public int Count { get; set; } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForEventTests.cs b/GFramework.Core.Tests/coroutine/WaitForEventTests.cs new file mode 100644 index 0000000..723aa4e --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForEventTests.cs @@ -0,0 +1,270 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.Abstractions.events; +using GFramework.Core.coroutine.instructions; +using Moq; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForEvent的单元测试类 +/// 测试内容包括: +/// - 初始化和基本功能 +/// - 事件触发处理 +/// - 资源释放 +/// - 异常处理 +/// +[TestFixture] +public class WaitForEventTests +{ + /// + /// 测试用的简单事件类 + /// + private class TestEvent + { + public string Data { get; set; } = string.Empty; + } + + /// + /// 验证WaitForEvent初始状态为未完成 + /// + [Test] + public void WaitForEvent_Should_Not_Be_Done_Initially() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var wait = new WaitForEvent(eventBusMock.Object); + + Assert.That(wait.IsDone, Is.False); + } + + /// + /// 验证WaitForEvent应该在事件触发后完成 + /// + [Test] + public void WaitForEvent_Should_Be_Done_After_Event_Triggered() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var wait = new WaitForEvent(eventBusMock.Object); + + Assert.That(wait.IsDone, Is.False); + + // 触发事件 + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForEvent应该保存事件数据 + /// + [Test] + public void WaitForEvent_Should_Save_Event_Data() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var wait = new WaitForEvent(eventBusMock.Object); + + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.EventData, Is.Not.Null); + Assert.That(wait.EventData?.Data, Is.EqualTo("TestData")); + } + + /// + /// 验证WaitForEvent应该在事件触发后保持完成状态 + /// + [Test] + public void WaitForEvent_Should_Remain_Done_After_Event_Triggered() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var wait = new WaitForEvent(eventBusMock.Object); + + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.IsDone, Is.True); + + // 再次触发事件,确认状态不变 + registeredAction?.Invoke(new TestEvent { Data = "AnotherData" }); + + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForEvent应该在Dispose后释放资源 + /// + [Test] + public void WaitForEvent_Should_Release_Resources_On_Dispose() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var wait = new WaitForEvent(eventBusMock.Object); + + wait.Dispose(); + + unRegisterMock.Verify(x => x.UnRegister(), Times.Once); + } + + /// + /// 验证WaitForEvent应该处理多次Dispose调用 + /// + [Test] + public void WaitForEvent_Should_Handle_Multiple_Dispose_Calls() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var wait = new WaitForEvent(eventBusMock.Object); + + wait.Dispose(); + // 第二次调用不应引发异常 + Assert.DoesNotThrow(() => wait.Dispose()); + } + + /// + /// 验证WaitForEvent应该抛出ArgumentNullException当eventBus为null + /// + [Test] + public void WaitForEvent_Should_Throw_ArgumentNullException_When_EventBus_Is_Null() + { + Assert.Throws(() => new WaitForEvent(null!)); + } + + /// + /// 验证WaitForEvent的Update方法不影响状态 + /// + [Test] + public void WaitForEvent_Update_Should_Not_Affect_State() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var wait = new WaitForEvent(eventBusMock.Object); + + wait.Update(0.1); + Assert.That(wait.IsDone, Is.False); + + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + wait.Update(0.1); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForEvent实现IYieldInstruction接口 + /// + [Test] + public void WaitForEvent_Should_Implement_IYieldInstruction() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var wait = new WaitForEvent(eventBusMock.Object); + + Assert.That(wait, Is.InstanceOf()); + } + + /// + /// 验证WaitForEvent在事件触发后自动注销监听器 + /// + [Test] + public void WaitForEvent_Should_Unregister_After_Event_Triggered() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var wait = new WaitForEvent(eventBusMock.Object); + + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + // 事件触发后,Update方法应该注销监听器 + wait.Update(0.1); + + unRegisterMock.Verify(x => x.UnRegister(), Times.AtLeastOnce); + } + + /// + /// 验证WaitForEventEventData为null当没有事件触发 + /// + [Test] + public void WaitForEvent_EventData_Should_Be_Null_When_No_Event_Triggered() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var wait = new WaitForEvent(eventBusMock.Object); + + Assert.That(wait.EventData, Is.Null); + } + + /// + /// 验证WaitForEventEventData在事件触发后不为null + /// + [Test] + public void WaitForEvent_EventData_Should_Not_Be_Null_After_Event_Triggered() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var wait = new WaitForEvent(eventBusMock.Object); + + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.EventData, Is.Not.Null); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/coroutine/WaitForEventWithTimeoutTests.cs b/GFramework.Core.Tests/coroutine/WaitForEventWithTimeoutTests.cs new file mode 100644 index 0000000..dc2ee1c --- /dev/null +++ b/GFramework.Core.Tests/coroutine/WaitForEventWithTimeoutTests.cs @@ -0,0 +1,328 @@ +using GFramework.Core.Abstractions.coroutine; +using GFramework.Core.Abstractions.events; +using GFramework.Core.coroutine.instructions; +using Moq; +using NUnit.Framework; + +namespace GFramework.Core.Tests.coroutine; + +/// +/// WaitForEventWithTimeout的单元测试类 +/// 测试内容包括: +/// - 初始化和基本功能 +/// - 超时处理 +/// - 事件提前触发 +/// - 异常处理 +/// +[TestFixture] +public class WaitForEventWithTimeoutTests +{ + /// + /// 测试用的简单事件类 + /// + private class TestEvent + { + public string Data { get; set; } = string.Empty; + } + + /// + /// 验证WaitForEventWithTimeout初始状态为未完成且未超时 + /// + [Test] + public void WaitForEventWithTimeout_Should_Not_Be_Done_Initially() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 2.0f); + + Assert.That(wait.IsDone, Is.False); + Assert.That(wait.IsTimeout, Is.False); + } + + /// + /// 验证WaitForEventWithTimeout应该在超时时完成 + /// + [Test] + public void WaitForEventWithTimeout_Should_Be_Done_When_Timeout() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 1.0f); + + // 更新时间超过超时时间 + wait.Update(1.5); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimeout, Is.True); + } + + /// + /// 验证WaitForEventWithTimeout应该在事件触发时完成 + /// + [Test] + public void WaitForEventWithTimeout_Should_Be_Done_When_Event_Triggered() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 2.0f); + + // 触发事件 + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimeout, Is.False); + } + + /// + /// 验证WaitForEventWithTimeout应该在事件触发后保存事件数据 + /// + [Test] + public void WaitForEventWithTimeout_Should_Save_Event_Data() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 2.0f); + + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.EventData, Is.Not.Null); + Assert.That(wait.EventData?.Data, Is.EqualTo("TestData")); + } + + /// + /// 验证WaitForEventWithTimeout应该在超时后返回null事件数据 + /// + [Test] + public void WaitForEventWithTimeout_Should_Return_Null_EventData_When_Timeout() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 1.0f); + + wait.Update(1.5); + + Assert.That(wait.EventData, Is.Null); + } + + /// + /// 验证WaitForEventWithTimeout应该在事件触发前正确计算超时 + /// + [Test] + public void WaitForEventWithTimeout_Should_Calculate_Timeout_Correctly() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 2.0f); + + wait.Update(1.0); + Assert.That(wait.IsTimeout, Is.False); + Assert.That(wait.IsDone, Is.False); + + wait.Update(0.5); + Assert.That(wait.IsTimeout, Is.False); + Assert.That(wait.IsDone, Is.False); + + wait.Update(0.6); // 总共2.1秒,超过2.0秒超时时间 + Assert.That(wait.IsTimeout, Is.True); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForEventWithTimeout应该在事件触发后忽略后续超时 + /// + [Test] + public void WaitForEventWithTimeout_Should_Ignore_Timeout_After_Event_Triggered() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 1.0f); + + // 触发事件 + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimeout, Is.False); + + // 即使时间超过了超时限制,也不应标记为超时 + wait.Update(2.0); + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimeout, Is.False); + } + + /// + /// 验证WaitForEventWithTimeout应该抛出ArgumentNullException当waitForEvent为null + /// + [Test] + public void WaitForEventWithTimeout_Should_Throw_ArgumentNullException_When_WaitForEvent_Is_Null() + { + Assert.Throws(() => new WaitForEventWithTimeout(null!, 1.0f)); + } + + /// + /// 验证WaitForEventWithTimeout应该正确处理Update方法 + /// + [Test] + public void WaitForEventWithTimeout_Update_Should_Work_Correctly() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 2.0f); + + // 更新时间但未超过超时时间 + wait.Update(1.0); + Assert.That(wait.IsDone, Is.False); + Assert.That(wait.IsTimeout, Is.False); + + // 触发事件 + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimeout, Is.False); + } + + /// + /// 验证WaitForEventWithTimeout实现IYieldInstruction接口 + /// + [Test] + public void WaitForEventWithTimeout_Should_Implement_IYieldInstruction() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 2.0f); + + Assert.That(wait, Is.InstanceOf()); + } + + /// + /// 验证WaitForEventWithTimeout应该处理小超时时间 + /// + [Test] + public void WaitForEventWithTimeout_Should_Handle_Small_Timeout() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 0.1f); + + wait.Update(0.2); + + Assert.That(wait.IsTimeout, Is.True); + Assert.That(wait.IsDone, Is.True); + } + + /// + /// 验证WaitForEventWithTimeout应该处理大超时时间 + /// + [Test] + public void WaitForEventWithTimeout_Should_Handle_Large_Timeout() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 10.0f); + + wait.Update(5.0); + + Assert.That(wait.IsTimeout, Is.False); + Assert.That(wait.IsDone, Is.False); + } + + /// + /// 验证WaitForEventWithTimeout应该在事件触发后忽略后续超时并保持状态 + /// + [Test] + public void WaitForEventWithTimeout_Should_Ignore_Timeout_And_Maintain_State_After_Event_Triggered() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + var waitForEvent = new WaitForEvent(eventBusMock.Object); + var wait = new WaitForEventWithTimeout(waitForEvent, 1.0f); + + // 触发事件 + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimeout, Is.False); + Assert.That(wait.EventData, Is.Not.Null); + Assert.That(wait.EventData?.Data, Is.EqualTo("TestData")); + + // 即使时间超过了超时限制,也不应标记为超时,状态应保持不变 + wait.Update(2.0); + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimeout, Is.False); + Assert.That(wait.EventData, Is.Not.Null); + Assert.That(wait.EventData?.Data, Is.EqualTo("TestData")); + + // 再次更新,状态仍应保持不变 + wait.Update(1.0); + Assert.That(wait.IsDone, Is.True); + Assert.That(wait.IsTimeout, Is.False); + Assert.That(wait.EventData, Is.Not.Null); + Assert.That(wait.EventData?.Data, Is.EqualTo("TestData")); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/events/EventListenerScopeTests.cs b/GFramework.Core.Tests/events/EventListenerScopeTests.cs new file mode 100644 index 0000000..b63e28f --- /dev/null +++ b/GFramework.Core.Tests/events/EventListenerScopeTests.cs @@ -0,0 +1,321 @@ +using GFramework.Core.Abstractions.events; +using GFramework.Core.events; +using Moq; +using NUnit.Framework; + +namespace GFramework.Core.Tests.events; + +/// +/// EventListenerScope的单元测试类 +/// 测试内容包括: +/// - 初始化和基本功能 +/// - 事件触发处理 +/// - 资源释放 +/// - 多次触发事件 +/// +[TestFixture] +public class EventListenerScopeTests +{ + /// + /// 测试用的简单事件类 + /// + private class TestEvent + { + public string Data { get; set; } = string.Empty; + } + + /// + /// 验证EventListenerScope初始状态为未触发 + /// + [Test] + public void EventListenerScope_Should_Not_Be_Triggered_Initially() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + using var scope = new EventListenerScope(eventBusMock.Object); + + Assert.That(scope.IsTriggered, Is.False); + } + + /// + /// 验证EventListenerScope初始EventData为null + /// + [Test] + public void EventListenerScope_EventData_Should_Be_Null_Initially() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + using var scope = new EventListenerScope(eventBusMock.Object); + + Assert.That(scope.EventData, Is.Null); + } + + /// + /// 验证EventListenerScope应该在事件触发后标记为已触发 + /// + [Test] + public void EventListenerScope_Should_Be_Triggered_After_Event_Fired() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + using var scope = new EventListenerScope(eventBusMock.Object); + + Assert.That(scope.IsTriggered, Is.False); + + // 触发事件 + var testEvent = new TestEvent { Data = "TestData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(scope.IsTriggered, Is.True); + } + + /// + /// 验证EventListenerScope应该保存事件数据 + /// + [Test] + public void EventListenerScope_Should_Save_Event_Data() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + using var scope = new EventListenerScope(eventBusMock.Object); + + var testEvent = new TestEvent { Data = "SavedData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(scope.EventData, Is.Not.Null); + Assert.That(scope.EventData?.Data, Is.EqualTo("SavedData")); + } + + /// + /// 验证EventListenerScope在Dispose后应该取消注册 + /// + [Test] + public void EventListenerScope_Should_UnRegister_On_Dispose() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var scope = new EventListenerScope(eventBusMock.Object); + + scope.Dispose(); + + unRegisterMock.Verify(x => x.UnRegister(), Times.Once); + } + + /// + /// 验证EventListenerScope应该在using块结束后自动取消注册 + /// + [Test] + public void EventListenerScope_Should_UnRegister_When_Using_Block_Ends() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + ExecuteUsingBlockTest(eventBusMock); + + // 验证using块结束后调用了UnRegister + unRegisterMock.Verify(x => x.UnRegister(), Times.Once); + } + + /// + /// 验证EventListenerScope应该在事件触发后保持已触发状态 + /// + [Test] + public void EventListenerScope_Should_Remain_Triggered_After_Event_Fired() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + using var scope = new EventListenerScope(eventBusMock.Object); + + var testEvent = new TestEvent { Data = "FirstData" }; + registeredAction?.Invoke(testEvent); + + Assert.That(scope.IsTriggered, Is.True); + + // 再次触发事件,状态应保持已触发 + registeredAction?.Invoke(new TestEvent { Data = "SecondData" }); + + Assert.That(scope.IsTriggered, Is.True); + } + + /// + /// 验证EventListenerScope在多次触发时应该保存最后一次的事件数据 + /// + [Test] + public void EventListenerScope_Should_Save_Last_Event_Data_On_Multiple_Triggers() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + using var scope = new EventListenerScope(eventBusMock.Object); + + // 第一次触发 + registeredAction?.Invoke(new TestEvent { Data = "FirstData" }); + Assert.That(scope.EventData?.Data, Is.EqualTo("FirstData")); + + // 第二次触发,应该覆盖数据 + registeredAction?.Invoke(new TestEvent { Data = "SecondData" }); + Assert.That(scope.EventData?.Data, Is.EqualTo("SecondData")); + } + + /// + /// 验证EventListenerScope应该在初始化时注册事件监听器 + /// + [Test] + public void EventListenerScope_Should_Register_Event_Listener_On_Init() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + using var scope = new EventListenerScope(eventBusMock.Object); + + eventBusMock.Verify(x => x.Register(It.IsAny>()), Times.Once); + } + + /// + /// 验证EventListenerScope应该处理值类型事件 + /// + [Test] + public void EventListenerScope_Should_Handle_Value_Type_Event() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + using var scope = new EventListenerScope(eventBusMock.Object); + + Assert.That(scope.IsTriggered, Is.False); + Assert.That(scope.EventData, Is.EqualTo(default(int))); + + registeredAction?.Invoke(42); + + Assert.That(scope.IsTriggered, Is.True); + Assert.That(scope.EventData, Is.EqualTo(42)); + } + + /// + /// 验证EventListenerScope应该处理结构体事件 + /// + [Test] + public void EventListenerScope_Should_Handle_Struct_Event() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + using var scope = new EventListenerScope(eventBusMock.Object); + + Assert.That(scope.IsTriggered, Is.False); + + var structEvent = new StructEvent { Id = 123, Value = 456.78f }; + registeredAction?.Invoke(structEvent); + + Assert.That(scope.IsTriggered, Is.True); + Assert.That(scope.EventData.Id, Is.EqualTo(123)); + Assert.That(scope.EventData.Value, Is.EqualTo(456.78f)); + } + + /// + /// 验证EventListenerScope应该是线程安全的(IsTriggered使用volatile) + /// + [Test] + public async Task EventListenerScope_IsTriggered_Should_Be_Thread_Safe() + { + Action? registeredAction = null; + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object) + .Callback>(action => registeredAction = action); + + using var scope = new EventListenerScope(eventBusMock.Object); + + // 在另一个线程中触发事件 + await Task.Run(() => { registeredAction?.Invoke(new TestEvent { Data = "ThreadData" }); }); + + // 主线程应该能看到更新后的值 + Assert.That(scope.IsTriggered, Is.True); + Assert.That(scope.EventData?.Data, Is.EqualTo("ThreadData")); + } + + /// + /// 验证EventListenerScope可以多次Dispose而不抛出异常 + /// + [Test] + public void EventListenerScope_Should_Handle_Multiple_Dispose_Calls() + { + var eventBusMock = new Mock(); + var unRegisterMock = new Mock(); + eventBusMock.Setup(x => x.Register(It.IsAny>())) + .Returns(unRegisterMock.Object); + + var scope = new EventListenerScope(eventBusMock.Object); + + // 多次调用Dispose不应抛出异常 + Assert.DoesNotThrow(() => scope.Dispose()); + Assert.DoesNotThrow(() => scope.Dispose()); + } + + /// + /// 测试用的结构体事件 + /// + private struct StructEvent + { + public int Id { get; set; } + public float Value { get; set; } + } + + /// + /// 辅助方法:执行using块测试 + /// + /// 事件总线模拟对象 + private static void ExecuteUsingBlockTest(Mock eventBusMock) + { + using var scope = new EventListenerScope(eventBusMock.Object); + // 作用域内部不验证 + } +} \ No newline at end of file