I want to mock the ResourceResponse from ReplaceDocumentAsync
Test
var document = new Document
{
// ...
};
var client = Substitute.For<IDocumentClient>();
var response = new ResourceResponse<Document>(document);
client.ReplaceDocumentAsync(Arg.Any<string>(), Arg.Any<ScheduleItemDal>(), Arg.Any<RequestOptions>()).Returns(response);
IResourceResponseBase response = await client.ReplaceDocumentAsync(selfLink, document, requestOptions);
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Accepted));
Unfortunately, I only get an NullReferenceException:
System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.Azure.Documents.Client.ResourceResponseBase.get_StatusCode()
I have the same problem with all other properties. What is the proper way to mock the properties of ResourceResponse?
apparently the issue is that the properties access an property that can only be accessed internally, so the end user has no way to set it in an unit test:
namespace Microsoft.Azure.Documents.Client
{
internal sealed class DocumentServiceResponse : IDisposable { /* ... */ }
public abstract class ResourceResponseBase : IResourceResponseBase
{
internal DocumentServiceResponse response; // no access possible; always null
public HttpStatusCode StatusCode { get => this.response.StatusCode; }
// ...
}
}
So I see multiple ways to solve this issue:
IDocumentClient, such that it requires only the base interfaces (ie. IResourceResponseBase) instead of the entities. However, this would further enforce the Entities Interface Antipattern. virtual in order to overwrite them in an derived test object class. internal to public and mark associated classes (ie. DocumentServiceResponse) as public. internal ResourceResponseBase.response field to an protected property (protected DocumentServiceResponse Response { get; }) and mark associated classes (ie. DocumentServiceResponse) as public. This allows setting the response from the derived test object. @MovGP0 mocking SDK is an existing gap that we are planning to address early next year.
Adding this to our backlog.
Currently it is not possible to mock FeedResponse.ResponseContinuation, since the required constructor is marked as internal. Here is my current workaround:
private static FeedResponse<T> CreateFeedResponse<T>(IEnumerable<T> items, string responseContinuation)
{
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;
var parameters = new object[]
{
items,
items.Count(),
new NameValueCollection
{
{"x-ms-continuation", responseContinuation}
},
0L
};
var culture = CultureInfo.InvariantCulture;
var activated = Activator.CreateInstance(typeof(FeedResponse<T>), flags, null, parameters, culture);
return (FeedResponse<T>)activated;
}
@MovGP0 I don't know if this changed in the 2.0.0 beta builds, but we are using that and your workaround was not enough as the FeedResponse constructors require a INameValueCollection property, not a "regular" NameValueCollection and that interface is itself internally declared.
Here's my awful hack on top of your workaround:
private static FeedResponse<TResult> CreateFeedResponse<TResult>(IEnumerable<TResult> items, string responseContinuation)
{
/*
* So the FeedResponse is a public class, but the ResponseContinuation property is defined
* via private fields that are only set via internal constructors.
* Furthermore, one of the properties we need is itself defined in an internal interface,
* So we have to first instantiate an instance of an internal class to the CosmosDB SDK and
* then call an internal constructor on FeedResponse to get the ResponseContinuation to work.
*/
var nameValueCollection = new NameValueCollection
{
{"x-ms-continuation", responseContinuation}
};
var nameValueCollectionWrapperType = typeof(IDocumentClient).Assembly
.GetType("Microsoft.Azure.Documents.Collections.NameValueCollectionWrapper");
// my initial attempt to get the specific constructor failed, so I took the easy way out and am just grabbing all of them, even though this is obviously less efficient
var constructors = nameValueCollectionWrapperType.GetConstructors();
object nameValueCollectionWrapper = null;
foreach (var c in constructors)
{
var ctorParams = c.GetParameters();
if (ctorParams.Length == 1 && ctorParams[0].ParameterType == typeof(NameValueCollection))
{
nameValueCollectionWrapper = c.Invoke(new[] {nameValueCollection});
break;
}
}
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;
var parameters = new[]
{
items,
items.Count(),
nameValueCollectionWrapper,
0L
};
var culture = CultureInfo.InvariantCulture;
var activated = Activator.CreateInstance(typeof(FeedResponse<TResult>), flags, null, parameters, culture);
return (FeedResponse<TResult>)activated;
}
I would strongly recommend guys from Microsoft to unit test their code as a consumer before releasing packages.
Hi Guys,
How to mock the Query metrics in FeedResponse, if I give the value for header "x-ms-documentdb-query-metrics" in the above code it is not working.
Can someone please help.
I would strongly recommend guys from Microsoft to unit test their code as a consumer before releasing packages.
More than that - try remembering to use SOLID principles, not just blogging about them. In particular, "Open For Extension", which is the opposite of "make everything internal so we don't have to worry about maintaining it". Gah.
@kirankumarkolli I checked out verson 2.2.1, and while it seems like there are interfaces for IResourceResponse<TResource>, etc the IDocumentClient interface still returns a concrete type where the properties are not virtual (and thus cannot be mocked for testing).
Are you still working on the testability story or am I just consuming the API incorrectly?
Any updates on this? It is a big pain for us too that we cannot mock
Totally sucks that we can't, now our code has to go uncovered
it's even worse. It's an indication that Microsoft hasn't covered that code with tests either, which means that I can't fully trust it.
This really stinks.
I've spent the past couple of hours trying to unit test my application's Cosmos storage code. I can't set the properties of the ResourceResponse<T> returned by IDocumentClient::Task<ResourceResponse<Document>> ReadDocumentAsync(Uri documentUri, RequestOptions options = null, CancellationToken cancellationToken = default (CancellationToken)). Because of this method signature, in my test I have to use an actual instance of a ResourceResponse<Document> as the return value of ReadDocumentAsync(), and I can't control the value that the RequestCharge getter returns for my ResourceResponse<Document> instance. Instead, my code just throws a NullReferenceException whenever it tries to access that property while running my unit test.
I'd expect IDocumentClient::Task<ResourceResponse<Document>> ReadDocumentAsync(Uri documentUri, RequestOptions options = null, CancellationToken cancellationToken = default (CancellationToken)) to return a Task<**IResourceResponse**<Document>>.
Especially since there is already an IResourceResponse class?
:'(
Hi @dylanhouston7 ,
There is a v3 SDK that is GA and one of the biggest improvements it's supportability for unit tests and mocking.
Most helpful comment
I would strongly recommend guys from Microsoft to unit test their code as a consumer before releasing packages.