Search code examples
c#asp.net-coremoqxunitmoq-3

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


I am trying to mock the below method but the updateRefundReqeust returns null instead updated record.

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
        }
}

Unit test and Mocking method

[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();
}

Is something missed in the Mocking? Please let me know if more stuffs are required to support the question.


Solution

  • Here's a couple of points:

    • You don't need to setup a Mock multiple times, if all the Mocked calls are to return the same value. One Setup will service all calls with the given result.

    • If you want the mocked dependency to return different canned values on each call, then use SetupSequence and then chain as many Returns / ReturnsAsync for each result. However, it looks like you need to intercept the parameter and mutate it, and return it - there are Returns overloads which capture the parameter.

    • For async methods, you'll want to use ReturnsAsync instead of Returns. Both of these have overloads allowing you to capture the passed parameter to the mocked method.

    • You probably don't want the overhead of a new thread with Task.Run. Task.FromResult will return you a task wrapping the result.

    • You typically won't want to Setup on a reference type unless you know for sure that the same reference instance will be passed to the dependency, since if any other instance is passed, the Mock will be ignored. It.Is/IsAny can be used as wildcards.

    I believe you want something along the lines of:

    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);
        });