superninja
superninja

Reputation: 3411

Constructor arguments cannot be passed for interface mocks (Mock IFeedResponse)

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.

Upvotes: 2

Views: 3351

Answers (2)

Nkosi
Nkosi

Reputation: 247413

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

//...

Upvotes: 3

Nick Chapsas
Nick Chapsas

Reputation: 7200

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.

Upvotes: 1

Related Questions