单元测试Mediator发布了预期的INotification

问题描述 投票:0回答:1

我有一个 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

我看不出有任何理由可以嘲笑这里的任何东西。我想将实际的实现用于所有事情。

c# unit-testing xunit mediatr mediator
1个回答
0
投票

我已经根据我的规格弄清楚了如何解决这个问题。

在测试项目中,实现一个可以订阅任何通知的通用

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
,进行以下更改:

  1. 添加对 MediatR 的支持
  2. 使用我们要订阅的通知实例化
    TestNotificationHandler
    (在本例中为
    MessagePublishedNotification
  3. 实现
    IDisposable
    以便在单元测试之间清除消息
  4. 更新单元测试以验证
    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();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.