Blazor Server迁移至MediatR后,如何测试INotification的预期发布?
验证MediatR通知发布的单元测试方案
问题背景
我在Blazor Server应用中将原有的事件驱动客户端更新逻辑迁移为MediatR通知模式后,功能已正常运行,但需要编写与原事件测试风格一致的单元测试——验证符合预期类型和值的INotification是否被正确发布,且全程使用实际实现、不Mock MediatR内部逻辑。
原基于事件的实现及测试(使用FluentAssertions+IMonitor)已稳定运行,迁移后MessageService改为依赖IMediator,通过_mediator.Publish发送MessagePublishedNotification通知。
解决方案:自定义测试用Mediator捕获通知
我们可以实现一个轻量的TestMediator类,它实现IMediator接口并记录所有发布的通知,以此替代真实环境中的MediatR实例,从而在测试中直接验证通知的发布情况,完全符合原测试风格且无需Mock。
1. 实现TestMediator
这个类的核心是维护一个已发布通知的列表,并重写Publish方法将通知加入列表:
public class TestMediator : IMediator { // 存储所有已发布的通知 public List<INotification> PublishedNotifications { get; } = new(); // 处理泛型Publish调用 public Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { PublishedNotifications.Add(notification); return Task.CompletedTask; } // 处理非泛型Publish调用 public Task Publish(object notification, CancellationToken cancellationToken = default) { if (notification is INotification notif) PublishedNotifications.Add(notif); return Task.CompletedTask; } // 测试中无需用到的Send方法可抛出异常或留空 public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default) { throw new NotImplementedException("测试场景无需使用Send方法"); } public Task Send(IRequest request, CancellationToken cancellationToken = default) { throw new NotImplementedException("测试场景无需使用Send方法"); } }
2. 迁移后的业务代码
MessageService和MessagePublishedNotification的实现如下:
public class MessageService { private readonly IMediator _mediator; public MessageService(IMediator mediator) { _mediator = mediator; } public async Task<bool> PublishMessage(DateTimeOffset timestamp, string username, string message) { if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(message)) { return false; } await _mediator.Publish(new MessagePublishedNotification(timestamp, username, message)); return true; } } public class MessagePublishedNotification : INotification { public MessagePublishedNotification(DateTimeOffset timestamp, string username, string message) { Timestamp = timestamp; Username = username; Message = message; } public DateTimeOffset Timestamp { get; init; } public string Username { get; init; } public string Message { get; init; } }
3. 编写单元测试
测试逻辑与原事件测试风格一致,通过验证TestMediator中记录的通知来确认业务行为:
public class MessageServiceTests { private readonly TestMediator _testMediator; private readonly MessageService _messageService; public MessageServiceTests() { _testMediator = new TestMediator(); _messageService = new MessageService(_testMediator); } [Fact] public async Task PublishMessage_ValidInput_PublishesExpectedNotification() { // Arrange var timestamp = DateTimeOffset.Now; var username = "Eric"; var message = "Hi! :)"; // Act var publishResult = await _messageService.PublishMessage(timestamp, username, message); // Assert publishResult.Should().BeTrue(); // 验证通知数量、类型及参数值 _testMediator.PublishedNotifications.Should().ContainSingle() .Which.Should().BeOfType<MessagePublishedNotification>() .Which.Matches(notification => notification.Timestamp.Equals(timestamp) && notification.Username.Equals(username) && notification.Message.Equals(message)); } [Fact] public async Task PublishMessage_InvalidInput_DoesNotPublishNotification() { // Arrange var timestamp = DateTimeOffset.Now; var username = string.Empty; // 无效输入 var message = "Hi! :)"; // Act var publishResult = await _messageService.PublishMessage(timestamp, username, message); // Assert publishResult.Should().BeFalse(); _testMediator.PublishedNotifications.Should().BeEmpty(); } }
方案说明
- 全程使用实际实现:
TestMediator是IMediator的真实实现,仅用于测试场景捕获通知,未Mock任何组件 - 测试风格一致:通过FluentAssertions的链式断言,与原事件测试的验证逻辑保持统一
- 灵活性高:如果后续需要验证通知被处理逻辑,可扩展
TestMediator添加Handler的注册和执行逻辑
内容的提问来源于stack exchange,提问作者dropbear
相关产品推荐
相关产品推荐

