我有一个 Blazor 服务器端应用程序,它使用事件触发客户端之间的更新。我决定将其迁移为使用 MediatR 来触发更新。实际的实现是有效的,但我有点停留在单元测试上。之前我使用
FluentAssertions
和 IMonitor
来验证事件是否已引发。现在我想用 MediatR 做类似的事情。我在 SO 上看到过与此相关的问题,人们回答“你不应该测试 MediatR 的内部结构”。就我而言,我确信我没有测试 MediatR 的内部结构。我想测试的是通知确实已发布,并且具有预期值。
首先,我将通过事件展示我的工作实现。这里重要的部分是一个服务,它执行一些业务逻辑并在一切看起来不错的情况下触发一个事件,一个类
UpdateNotifier
公开事件和触发它们的方法,最后有一个测试服务的单元测试。
public class MessageService
{
private readonly IUpdateNotifier _notifier;
public MessageService(IUpdateNotifier notifier)
{
_notifier = notifier;
}
public async Task<bool> PublishMessage(DateTimeOffset timestamp, string username, string message)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(message))
{
return false;
}
_notifier.FireMessagePublished(timestamp, username, message);
return true;
}
}
public class UpdateNotifier : IUpdateNotifier
{
public event EventHandler<MessagePublishedEventArgs>? MessagePublished;
public void FireMessagePublished(DateTimeOffset timestamp, string username, string message)
{
MessagePublished?.Invoke(this, new MessagePublishedEventArgs(timestamp, username, message));
}
}
public class MessagePublishedEventArgs
{
public MessagePublishedEventArgs(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; }
}
public class MessageServiceTests
{
private readonly IUpdateNotifier _notifier;
private readonly MessageService _messageService;
public MessageServiceTests()
{
_notifier = new UpdateNotifier();
_messageService = new MessageService(_notifier);
}
[Fact]
public async Task PublishMessage_MessagePublished_HasExpectedValues()
{
// Arrange
var timestamp = DateTimeOffset.Now;
var username = "Eric";
var message = "Hi! :)";
using var updateNotifierMonitor = _notifier.Monitor();
// Act
var publishMessageResult = await _messageService.PublishMessage(timestamp, username, message);
// Assert
publishMessageResult.Should().BeTrue();
updateNotifierMonitor.Should()
.Raise(nameof(_notifier.MessagePublished))
.WithArgs<MessagePublishedEventArgs>(args => args.Timestamp.Equals(timestamp) && args.Username.Equals(username) && args.Message.Equals(message));
}
}
在我的迁移中,我所做的更改是删除
IUpdateNotifier
/UpdateNotifier
。 MessageService
依赖于 IMediator
。而不是 MessagePublishedEventArgs
,有一个 MessagePublishedNotification
实现了 INotification
。
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;
}
有没有办法以与我原来的单元测试相同的方式编写单元测试,但验证已发布的预期类型和预期值的
INotification
?
我看不出有任何理由可以嘲笑这里的任何东西。我想将实际的实现用于所有事情。
我已经根据我的规格弄清楚了如何解决这个问题。
在测试项目中,实现一个可以订阅任何通知的通用
INotificationHandler
:
public class TestNotificationHandler<TNotification> : INotificationHandler<TNotification>, IDisposable
where TNotification : INotification
{
internal List<TNotification> Notifications { get; } = new();
public async Task Handle(TNotification notification, CancellationToken cancellationToken)
{
Notifications.Add(notification);
await Task.CompletedTask;
}
public void Dispose()
{
Notifications.Clear();
}
}
更新
MessageServiceTests
,进行以下更改:
TestNotificationHandler
(在本例中为 MessagePublishedNotification
)IDisposable
以便在单元测试之间清除消息TestNotificationHandler
收到通知public class MessageServiceTests : IDisposable
{
private readonly MessageService _messageService;
private readonly TestNotificationHandler<MessagePublishedNotification> _notificationHandler;
public MessageServiceTests()
{
var services = new ServiceCollection();
services.AddMediator();
_notificationHandler = new TestNotificationHandler<MessagePublishedNotification>();
services.AddTransient<INotificationHandler<MessagePublishedNotification>>(_ => _notificationHandler);
_messageService = new MessageService(services.BuildServiceProvider().GetRequiredService<IMediator>());
}
[Fact]
public async Task PublishMessage_MessagePublished_HasExpectedValues()
{
// Arrange
var timestamp = DateTimeOffset.Now;
var username = "Eric";
var message = "Hi! :)";
// Act
var publishMessageResult = await _messageService.PublishMessage(timestamp, username, message);
// Assert
publishMessageResult.Should().BeTrue();
_notificationHandler.Notifications.Should()
.Contain(n => n.Timestamp == timestamp && n.Username == username && n.Message == message);
}
public void Dispose()
{
_notificationHandler.Dispose();
}
}