如何在 C#.NET 中使用 Moq 模拟在单次调用中多次调用的方法?

How to mock a method that is called multiple times in single call using Moq in C#.NET?

我正在尝试 mock 下面的方法但是 updateRefundReqeust returns null 而不是更新记录。

public async Task<bool> InvokeAsync(Batch batch)
{
        var refundRequests = await this.RefundRequestRepository.GetsAsync(batch.RefundRequests.Select(x => x.Id));

        foreach (var getRefundRequest in refundRequests)
        {
            getRefundRequest.Status = RefundRequestStatus.Closed;
            getRefundRequest.LastUpdatedBy = "Test User";

            RefundRequest updateRefundReqeust = await UpdateRefund.InvokeAsync(getRefundRequest);
           //Returns null instead of updated record
        }
}

单元测试和 Mocking 方法

[Fact]
public async Task Post_Batch()
{
    var refundRequests = await this.RefundRequestRepository.GetsAsync(batch.RefundRequests.Select(x => x.Id));

    foreach (var getRefundRequest in refundRequests)
    {
        this.MockUpdateRefund
        .Setup(x => x.InvokeAsync(getRefundRequest))
        .Returns(async () =>
        {
            getRefundRequest.Status = RefundRequestStatus.Closed;
            getRefundRequest.LastUpdatedBy = Factory.RefundRequest.TestUserName;
            return await Task.Run(() => getRefundRequest);
        });
    }

    var postRefund = await PostBatch.InvokeAsync(batch);

    //Assert
    postRefund.ShouldNotBeNull();
    postRefund.IsPosted.ShouldBeTrue();
}

Mocking 中是否遗漏了什么?如果需要更多的东西来支持这个问题,请告诉我。

这里有几点:

  • 如果所有 Mocked 调用都是 return 相同的值,则无需多次设置 Mock。一个设置将为具有给定结果的所有调用提供服务。

  • 如果您希望模拟依赖项在每次调用时 return 不同的固定值,则使用 SetupSequence 然后链接尽可能多的 Returns / ReturnsAsync 对于每个结果。但是,看起来您需要拦截参数并对其进行变异,并且 return 它 - 有 Returns 重载捕获参数。

  • 对于异步方法,您需要使用 而不是 Returns。这两个都有重载,允许您将传递的参数捕获到模拟方法。

  • 您可能不希望使用 Task.Run 的新线程的开销。 Task.FromResult 将 return 你一个包装结果的任务。

  • 您通常不想在引用类型上设置,除非您确定相同的引用实例将传递给依赖项,因为如果传递任何其他实例,Mock 将被忽略。 It.Is/IsAny 可以用作通配符。

我相信你想要的是:

this.MockUpdateRefund
    .Setup(x => x.InvokeAsync(It.IsAny<RefundRequest>()))
    .ReturnsAsync<RefundRequest>((RefundRequest getRefundRequest) =>
    {
        getRefundRequest.Status = RefundRequestStatus.Closed;
        getRefundRequest.LastUpdatedBy = Factory.RefundRequest.TestUserName;
        return Task.FromResult(getRefundRequest);
    });