refactor(coroutine): 移除命令等待事件协程扩展并改进测试

- 从 MediatorCoroutineExtensions 中移除 SendCommandAndWaitEventCoroutine 相关代码
- 从 CommandCoroutineExtensionsTests 中移除对应的测试方法
- 更新测试用例验证逻辑,统一使用静态方法调用方式
- 添加新的 MediatorCoroutineExtensionsTests 测试类
- 修改测试方法名称以更准确反映测试行为
- 统一异常处理和参数验证的测试覆盖
This commit is contained in:
GeWuYou 2026-02-16 22:40:14 +08:00 committed by gewuyou
parent 855b3f9eac
commit 984829c368
3 changed files with 151 additions and 131 deletions

View File

@ -133,10 +133,10 @@ public class CommandCoroutineExtensionsTests
}
/// <summary>
/// 验证SendCommandCoroutineWithErrorHandler应该处理null错误回调
/// 验证SendCommandCoroutineWithErrorHandler在无错误处理程序时应该抛出异常
/// </summary>
[Test]
public async Task SendCommandCoroutineWithErrorHandler_Should_Handle_Null_Error_Handler()
public void SendCommandCoroutineWithErrorHandler_Should_Throw_Exception_Without_Handler()
{
var command = new TestCommand();
var contextAware = new TestContextAware();
@ -147,21 +147,19 @@ public class CommandCoroutineExtensionsTests
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
.Returns(Task.FromException(expectedException));
// 使用null作为错误处理程序
var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command);
// 迭代协程直到完成
while (coroutine.MoveNext())
// 迭代协程应该抛出异常
Assert.Throws<InvalidOperationException>(() =>
{
if (coroutine.Current is WaitForTask waitForTask)
while (coroutine.MoveNext())
{
// 等待任务完成
await Task.Delay(10);
if (coroutine.Current is WaitForTask waitForTask)
{
Task.Delay(10).Wait();
}
}
}
// 应该不会抛出异常
Assert.Pass();
});
}
/// <summary>
@ -194,7 +192,8 @@ public class CommandCoroutineExtensionsTests
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
.Returns(Task.CompletedTask);
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
contextAware,
command,
ev =>
{
@ -250,7 +249,8 @@ public class CommandCoroutineExtensionsTests
// 对于超时情况我们期望抛出TimeoutException
Assert.Throws<TimeoutException>(() =>
{
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
contextAware,
command,
null,
0.1f); // 0.1秒超时
@ -292,7 +292,8 @@ public class CommandCoroutineExtensionsTests
.Returns(Task.CompletedTask);
// 使用null作为事件回调
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
contextAware,
command); // null回调
// 启动协程
@ -334,7 +335,8 @@ public class CommandCoroutineExtensionsTests
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
.Returns(Task.FromException(expectedException));
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
contextAware,
command,
_ => { });
@ -391,7 +393,8 @@ public class CommandCoroutineExtensionsTests
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
.Returns(Task.CompletedTask);
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
contextAware,
command,
ev => { });
@ -399,10 +402,30 @@ public class CommandCoroutineExtensionsTests
}
/// <summary>
/// 验证SendCommandAndWaitEventCoroutine应该在事件总线为null时处理异常
/// 验证SendCommandAndWaitEventCoroutine应该在timeout小于0时抛出ArgumentOutOfRangeException
/// </summary>
[Test]
public void SendCommandAndWaitEventCoroutine_Should_Handle_Null_EventBus()
public void SendCommandAndWaitEventCoroutine_Should_Throw_ArgumentOutOfRange_When_Timeout_Negative()
{
var command = new TestCommand();
var contextAware = new TestContextAware();
// 在创建协程时就应该抛出异常
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
contextAware,
command,
null,
-1.0f);
});
}
/// <summary>
/// 验证SendCommandAndWaitEventCoroutine应该在事件总线为null时抛出InvalidOperationException
/// </summary>
[Test]
public void SendCommandAndWaitEventCoroutine_Should_Throw_When_EventBus_Null()
{
var command = new TestCommand();
var contextAware = new TestContextAware();
@ -413,11 +436,12 @@ public class CommandCoroutineExtensionsTests
.Returns((IEventBus?)null);
// 创建协程
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
contextAware,
command,
ev => { });
// 调用 MoveNext 时应该抛出异常因为事件总线为null
Assert.Throws<ArgumentNullException>(() => coroutine.MoveNext());
// 调用 MoveNext 时应该抛出 InvalidOperationException
Assert.Throws<InvalidOperationException>(() => coroutine.MoveNext());
}
}

View File

@ -0,0 +1,105 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.coroutine;
using GFramework.Core.Abstractions.rule;
using GFramework.Core.coroutine.extensions;
using Mediator;
using Moq;
using NUnit.Framework;
namespace GFramework.Core.Tests.coroutine;
/// <summary>
/// MediatorCoroutineExtensions的单元测试类
/// 测试Mediator模式与协程集成的扩展方法
/// 注意:由于 Mediator 使用源生成器,本测试类主要验证接口和参数验证
/// </summary>
[TestFixture]
public class MediatorCoroutineExtensionsTests
{
/// <summary>
/// 测试用的简单命令类
/// </summary>
private class TestCommand : IRequest<Unit>
{
public string Data { get; set; } = string.Empty;
}
/// <summary>
/// 测试用的简单事件类
/// </summary>
private class TestEvent
{
public string Data { get; set; } = string.Empty;
}
/// <summary>
/// 上下文感知基类的模拟实现
/// </summary>
private class TestContextAware : IContextAware
{
public readonly Mock<IArchitectureContext> _mockContext = new();
public IArchitectureContext GetContext()
{
return _mockContext.Object;
}
public void SetContext(IArchitectureContext context)
{
}
}
/// <summary>
/// 验证SendCommandCoroutine应该返回IEnumerator<IYieldInstruction>
/// </summary>
[Test]
public void SendCommandCoroutine_Should_Return_IEnumerator_Of_YieldInstruction()
{
var command = new TestCommand { Data = "Test" };
var contextAware = new TestContextAware();
// 创建 mediator 模拟
var mediatorMock = new Mock<IMediator>();
contextAware._mockContext
.Setup(ctx => ctx.GetService<IMediator>())
.Returns(mediatorMock.Object);
var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command);
Assert.That(coroutine, Is.InstanceOf<IEnumerator<IYieldInstruction>>());
}
/// <summary>
/// 验证SendCommandCoroutine应该在mediator为null时抛出NullReferenceException
/// </summary>
[Test]
public void SendCommandCoroutine_Should_Throw_When_Mediator_Null()
{
var command = new TestCommand { Data = "Test" };
var contextAware = new TestContextAware();
// 设置上下文服务以返回null mediator
contextAware._mockContext
.Setup(ctx => ctx.GetService<IMediator>())
.Returns((IMediator?)null);
// 创建协程
var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command);
// 调用 MoveNext 时应该抛出 NullReferenceException
Assert.Throws<NullReferenceException>(() => coroutine.MoveNext());
}
}

View File

@ -12,9 +12,7 @@
// limitations under the License.
using GFramework.Core.Abstractions.coroutine;
using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.rule;
using GFramework.Core.coroutine.instructions;
using Mediator;
namespace GFramework.Core.coroutine.extensions;
@ -53,111 +51,4 @@ public static class MediatorCoroutineExtensions
else
throw task.Exception!.InnerException ?? task.Exception;
}
// ... existing code ...
/// <summary>
/// 发送命令并等待特定事件的协程实现。
/// </summary>
/// <typeparam name="TCommand">命令类型</typeparam>
/// <typeparam name="TEvent">要等待的事件类型</typeparam>
/// <param name="contextAware">上下文对象</param>
/// <param name="command">要发送的命令</param>
/// <param name="onEvent">事件触发时的回调</param>
/// <param name="timeout">
/// 超时时间(秒):
/// <list type="bullet">
/// <item><description>timeout &lt; 0: 无效,将抛出 ArgumentOutOfRangeException</description></item>
/// <item><description>timeout == 0: 无超时,永久等待</description></item>
/// <item><description>timeout &gt; 0: 启用超时机制</description></item>
/// </list>
/// </param>
/// <exception cref="ArgumentOutOfRangeException">当 timeout 小于 0 时抛出。</exception>
public static IEnumerator<IYieldInstruction> SendCommandAndWaitEventCoroutine<TCommand, TEvent>(
this IContextAware contextAware,
TCommand command,
Action<TEvent>? onEvent = null,
float timeout = 0f)
where TCommand : notnull
where TEvent : class
{
// 参数验证
ValidateParameters(timeout);
// 获取必要的服务
var context = contextAware.GetContext();
var mediator = context.GetService<IMediator>()
?? throw new InvalidOperationException("IMediator not found.");
var eventBus = context.GetService<IEventBus>()
?? throw new InvalidOperationException("IEventBus not found.");
// 执行协程逻辑
return ExecuteSendCommandAndWaitEventCoroutine(mediator, eventBus, command, onEvent, timeout);
}
/// <summary>
/// 验证方法参数的有效性。
/// </summary>
/// <param name="timeout">超时时间</param>
/// <exception cref="ArgumentOutOfRangeException">当 timeout 小于 0 时抛出。</exception>
private static void ValidateParameters(float timeout)
{
if (timeout < 0f)
throw new ArgumentOutOfRangeException(
nameof(timeout),
timeout,
"Timeout must be greater than or equal to 0.");
}
/// <summary>
/// 执行发送命令并等待事件的协程逻辑。
/// </summary>
/// <typeparam name="TCommand">命令类型</typeparam>
/// <typeparam name="TEvent">事件类型</typeparam>
/// <param name="mediator">中介者服务</param>
/// <param name="eventBus">事件总线服务</param>
/// <param name="command">要发送的命令</param>
/// <param name="onEvent">事件回调</param>
/// <param name="timeout">超时时间</param>
/// <returns>协程枚举器</returns>
private static IEnumerator<IYieldInstruction> ExecuteSendCommandAndWaitEventCoroutine<TCommand, TEvent>(
IMediator mediator,
IEventBus eventBus,
TCommand command,
Action<TEvent>? onEvent,
float timeout)
where TCommand : notnull
where TEvent : class
{
WaitForEvent<TEvent>? wait = null;
try
{
wait = new WaitForEvent<TEvent>(eventBus);
var task = mediator.Send(command).AsTask();
yield return task.AsCoroutineInstruction();
if (timeout > 0f)
{
var timeoutWait = new WaitForEventWithTimeout<TEvent>(wait, timeout);
yield return timeoutWait;
if (timeoutWait.IsTimeout)
throw new TimeoutException(
$"Wait for event {typeof(TEvent).Name} timeout.");
}
else
{
yield return wait;
}
if (wait.EventData != null)
onEvent?.Invoke(wait.EventData);
}
finally
{
wait?.Dispose();
}
}
// ... existing code ...
}