web-dev-qa-db-ja.com

MediatR3をMoqでモックする

最近、MediatRの使用を開始して、大規模な顧客向けポータルをリファクタリングし、すべてをC#に変換するときに、コントローラーアクションを整理できるようにしました。その一環として、ユニットテストのカバレッジも拡大していますが、MediatR自体をモックしようとすると問題が発生しました。

コマンドはプロセスを開始するためにたくさんのことを行い、その一部は通知を送信します。通知自体は独自のハンドラーによって処理されるため、独自の単体テストの対象となるため、MediatRをモックして、this.mediator.Send(message)呼び出しが実際には何もしないようにします。ハンドラーはオブジェクトを返しますが、このコンテキストでは気にしないので、すべての意図と目的で、オブジェクトをvoidリターンとして扱います。テストの一環としてSendが一度呼び出されたことを確認したいだけです。ただし、SendメソッドはNullReferenceExceptionをスローしているため、理由はわかりません。

バージョン3以降、MediatRはSendCancellationTokenで2番目のオプションのパラメーターを取り、式ツリーでは明示的に設定する必要があるため、値を指定する必要があります。私はこれまでこれに遭遇したことがなく、私の心の中でこれは問題の一部かもしれないと感じていますが、これは私の側の混乱かもしれません。

これが切り詰められたイラストです。

SUT

public class TransferHandler : IAsyncRequestHandler<TransferCommand, TransferResult>
{
    private readonly IMediator mediator;

    public TransferHandler(IMediator mediator)
    {
        this.mediator = mediator;
    }

    public async Task<TransferResult> Handle(TransferCommand message)
    {
        // Other stuff.
        var notification = new TransferNotificationCommand()
        {
            ClientId = message.clientId,
            OfficeId = message.OfficeId,
            AuthorityFileId = letter?.Id
        };

        await this.mediator.Send(notification);    // <=== This is where we get a NullReferenceException, even though nothing is actually null (that I can see).

        return new TransferResult()
        {
            Transfer = transfer,
            FileId = letter?.Id
        }
    }
}

テスト

public class TransferHandlerTests
{
    [Theory]
    [AutoData]
    public async void HandlerCreatesTransfer(Mock<IMediator> mockMediator)
    {
        // Note that default(CancellationToken) is the default value of the optional argument.
        mockMediator.Setup(m => m.Send(It.IsAny<TransferNotificationCommand>(), default(CancellationToken))).Verifiable("Notification was not sent.");

        var handler = new TransferHandler(mockMediator.Object);

        var actual = await handler.Handle(message);

        mockMediator.Verify(x => x.Send(It.IsAny<CreateIsaTransferNotificationCommand>(), default(CancellationToken)), Times.Once());
    }
}

何が足りないのですか?どこかで根本的なミスをしたような気がしますが、どこかわかりません。

10
Steve Pettifer

Sendメソッドがタスクを返すときに、非同期操作の待機を処理する必要があります。

/// <summary>
/// Asynchronously send a request to a single handler
/// </summary>
/// <typeparam name="TResponse">Response type</typeparam>
/// <param name="request">Request object</param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>A task that represents the send operation. The task result contains the handler response</returns>
Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Asynchronously send a request to a single handler without expecting a response
/// </summary>
/// <param name="request">Request object</param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>A task that represents the send operation.</returns>
Task Send(IRequest request, CancellationToken cancellationToken = default(CancellationToken));

つまり、非同期プロセスがフローを続行できるようにするには、モックにタスクを返す必要があります。

mediator
    .Setup(m => m.Send(It.IsAny<TransferNotificationCommand>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(new Notification()) //<-- return Task to allow await to continue
    .Verifiable("Notification was not sent.");

//...other code removed for brevity

mediator.Verify(x => x.Send(It.IsAny<CreateIsaTransferNotificationCommand>(), It.IsAny<CancellationToken>()), Times.Once());
16
Nkosi