2

I am trying to write unit tests for DocumentDBRepository paging code. Since there is continuation token involved in the FeedResponse, I need to mock the FeedResponse in order to put some value for FeedResponse.ContinuationToken. But the problem is that I got an error saying:

Message: System.ArgumentException : Constructor arguments cannot be passed for interface mocks.

Does it mean I am not able to mock FeedResponse? Or maybe the way I use FeedResponse is wrong?

Here's my code:

var response = new Mock<IFeedResponse<T>>(expected);
response.Setup(_ => _.ResponseContinuation).Returns(It.IsAny<string>());
var mockDocumentQuery = new Mock<IFakeDocumentQuery<T>>();

mockDocumentQuery
    .SetupSequence(_ => _.HasMoreResults)
    .Returns(true)
    .Returns(false);

mockDocumentQuery
    .Setup(_ => _.ExecuteNextAsync<T>(It.IsAny<CancellationToken>()))
    .Returns((Task<FeedResponse<T>>)response.Object);

When I debugged, the break point stops at var response = new Mock<IFeedResponse<T>>(expected); and then the error happened.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
superninja
  • 3,114
  • 7
  • 30
  • 63
  • 1
    You are mocking interface so you don't need to pass the constructor arguments of class while creating mock. You simply need to do `var response = new Mock>();` – Chetan Aug 07 '18 at 23:11
  • @ChetanRanpariya hmm, since the `expected` is the expected return value from CosmosDB, then where do I pass this value? – superninja Aug 07 '18 at 23:19
  • 1
    You need to set this as a return value when you arrange expectations for the mock. – Chetan Aug 08 '18 at 00:07
  • 1
    @WendyW. You can use an actual instance of `FeedResponse`. no need to mock it. Similar to one of the answers I gave to one of our earlier questions here https://stackoverflow.com/a/49911733/5233410 – Nkosi Aug 08 '18 at 00:20
  • @Nkosi I did try that but since i have FeedResponse.ContinuationToken, it broke the unit tests.. So I have to figure out how to put the value for the token for the feed response in the tests.. – superninja Aug 08 '18 at 00:22
  • Ok that makes sense. thing is you were mocking the interface and trying to pass a constructor argument. That wont work. check to see if the class has the member you want to mock as virtual. or if you can set it manually. – Nkosi Aug 08 '18 at 00:23
  • @ChetanRanpariya I see.. but do you know which property of the `FeedResponse` should I assign to for the expected value returned from CosmosDB? – superninja Aug 08 '18 at 20:15
  • I have no knowledge about cosmosdb and how to write code for that. Can you share the code which you are trying to unit test? I will be able to suggest how to use mock. – Chetan Aug 08 '18 at 21:21

2 Answers2

3

The error is because you were mocking the interface and trying to pass a constructor argument. That wont work as stated by the error message.

You can however use an actual instance of FeedResponse.

Given that the desired member is not virtual and is also read-only, you could consider stubbing the class and overriding the default behavior since FeedResponse<T> is not sealed.

For example

public class FeedResponseStub<T> : FeedResponse<T> {
    private string token;

    public FeedResponseStub(IEnumerable<T> result, string token)
        : base(result) {
        this.token = token;
    }

    public new string ResponseContinuation {
        get {
            return token;
        }
    }
}

and using the stub in the test

//...

var token = ".....";
var response = new FeedResponseStub<T>(expected, token);

//...

mockDocumentQuery
    .Setup(_ => _.ExecuteNextAsync<T>(It.IsAny<CancellationToken>()))
    .ReturnsAsync(response);

//...
Nkosi
  • 235,767
  • 35
  • 427
  • 472
1

Here is the way I work around it in Cosmonaut.

public static FeedResponse<T> ToFeedResponse<T>(this IQueryable<T> resource, IDictionary<string, string> responseHeaders = null)
    {
        var feedResponseType = Type.GetType("Microsoft.Azure.Documents.Client.FeedResponse`1, Microsoft.Azure.DocumentDB.Core, Version=1.9.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");

        var flags = BindingFlags.NonPublic | BindingFlags.Instance;

        var headers = new NameValueCollection
        {
            { "x-ms-request-charge", "0" },
            { "x-ms-activity-id", Guid.NewGuid().ToString() }
        };

        if (responseHeaders != null)
        {
            foreach (var responseHeader in responseHeaders)
            {
                headers[responseHeader.Key] = responseHeader.Value;
            }
        }

        var arguments = new object[] { resource, resource.Count(), headers, false, null };

        if (feedResponseType != null)
        {
            var t = feedResponseType.MakeGenericType(typeof(T));

            var feedResponse = Activator.CreateInstance(t, flags, null, arguments, null);

            return (FeedResponse<T>)feedResponse;
        }

        return new FeedResponse<T>();
    }
}

You can pass your continuation token as a header key-value in the dictionary to set the FeedResponse value. You can do that by setting the x-ms-continuation value to a token.

Keep in mind that the ResponseContinuation property of the FeedResponse also takes the useETagAsContinuation value into account. I default it to false in the reflection invoked constructor.

For any further reference check the project's code and how unit tests are written.

Nick Chapsas
  • 6,872
  • 1
  • 20
  • 29