Open yyanev opened 7 years ago
@yyanev for mocking we need to look at it holistically across all SDK code. I will mark this for backlog.
I have the same problem now. I would need to create a DocumentQuery
, but I am unable to do so, because DocumentQuery
and DocumentQueryable.CreateDocumentQuery
are both marked as internal:
internal sealed class DocumentQuery<T>
: IDocumentQuery<T>, IDocumentQuery, IDisposable, IOrderedQueryable<T>, IEnumerable<T>, IEnumerable, IOrderedQueryable, IQueryable, IQueryable<T> { /* ... */ }
Same boat here. Since changing my production code to access RequestCharge and ETag in the ResourceResponse, my test code blows up with NullReferenceExceptions
For anyone else who runs into the issue of mocking a ResourceResponse, you can use reflection as a temporary workaround. Using the CosmosDB Emulator was not a viable option for us. Tests go to go fast. 🐎
You can use the following helper method to create fake ResourceResponse objects with specific status codes and Request Charges. (With slight modifications, you can also set various other properties). You'll then use some sort of simple mocking to have the mocked DocumentClient return this fake response.
@mattfrear , I don't think ResourceResponse
has an Etag property. Maybe you meant Document
?
/// <summary>
/// Various parts of the Azure DocumentDb SDK are not mockable due to missing constructors/read-only properties.
/// To get around this. we use reflection to create mocks.
/// ---
/// Create a resource response from a document using reflection
/// </summary>
/// <param name="resource">The Document</param>
/// <param name="statusCode">The status code this resource response should have.</param>
/// <returns></returns>
private ResourceResponse<Document> CreateResourceResponse(Document resource, HttpStatusCode statusCode)
{
// Skeleton of the ResourceResponse object we want to create.
// It doesn't have setters for properties like StatusCode and Request Charge.
// To set them, we need to access it's internal DocumentServiceResponse property
// and set them there.
var resourceResponse = new ResourceResponse<Document>(resource);
// Get the DocumentServiceResponse Type object
var documentServiceResponseType = Type.GetType("Microsoft.Azure.Documents.DocumentServiceResponse, Microsoft.Azure.Documents.Client, Version=1.19.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
// The constructor of DocumentServiceResponse (internal property of ResourceResponse)
// accepts as one of its arguments, a name-value collection.
// Some of ResourceResponse properties are retrieved via
// the contents of this name-value collection.
var headers = new NameValueCollection();
// Add the request charge to the header.
// If you need to mock the request charge for some reason, you can pass it into
// this helper method and set it here.
headers.Add("x-ms-request-charge", "0");
// The status code property is set in the constructor,
// so include it as an argument that we will pass to the constructor.
var arguments = new object[] { Stream.Null, headers, statusCode, null };
// This will invoke the DocumentServiceResponse constructor and set the status code.
// Since the request charge is in the header name-value collection,
// it is now available for "getting" from the enclosing ResourceResponse object
var documentServiceResponse = Activator.CreateInstance(documentServiceResponseType, flags, null, arguments, null);
// Get the DocumentServiceResponse field in the ResourceResponse<Document> Type
var responseField = typeof(ResourceResponse<Document>).GetField("response", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
// Set it! phew.
responseField.SetValue(resourceResponse, documentServiceResponse);
return resourceResponse;
}
Edit: The names of the keys in the key-value collection can be found by de-compiling the Microsoft.Azure.Documents.Client
dll
the same goes for FeedResponse
In addition, IDocumentClient should describe all the methods defined on DocumentClient concrete class.
Hi @eanyanwu
Thanks for your solution. When I tried using it I am getting constructor not found error at below line.
var documentServiceResponse = Activator.CreateInstance(documentServiceResponseType, flags, null, arguments, null);
Assembly Details:
Name | Value | Type | |
---|---|---|---|
▶ | Assembly | {Microsoft.Azure.Documents.Client, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35} | System.Reflection.Assembly {System.Reflection.RuntimeAssembly} |
Error: System.MissingMethodException: 'Constructor on type 'Microsoft.Azure.Documents.DocumentServiceResponse' not found.'
Please suggest what should I change?
Thanks in advance!
Please respond
You can check some TestingExtensions I wrote @joshidp.
Here is the extension method that convert any object to a ResourceReponse
.
public static ResourceResponse<T> ToResourceResponse<T>(this T resource, HttpStatusCode statusCode, IDictionary<string, string> responseHeaders = null) where T : Resource, new()
{
var resourceResponse = new ResourceResponse<T>(resource);
var documentServiceResponseType = Type.GetType("Microsoft.Azure.Documents.DocumentServiceResponse, 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" } };
if (responseHeaders != null)
{
foreach (var responseHeader in responseHeaders)
{
headers[responseHeader.Key] = responseHeader.Value;
}
}
var arguments = new object[] { Stream.Null, headers, statusCode, null };
var documentServiceResponse =
documentServiceResponseType.GetTypeInfo().GetConstructors(flags)[0].Invoke(arguments);
var responseField = typeof(ResourceResponse<T>).GetTypeInfo().GetField("response", BindingFlags.NonPublic | BindingFlags.Instance);
responseField?.SetValue(resourceResponse, documentServiceResponse);
return resourceResponse;
}
This will only work for pre-2.0.0 SDK versions.
For post 2.0.0 use this one instead.
public static ResourceResponse<T> ToResourceResponse<T>(this T resource, HttpStatusCode statusCode, IDictionary<string, string> responseHeaders = null) where T : Resource, new()
{
var resourceResponse = new ResourceResponse<T>(resource);
var documentServiceResponseType = Type.GetType("Microsoft.Azure.Documents.DocumentServiceResponse, Microsoft.Azure.DocumentDB.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var headers = new NameValueCollection { { "x-ms-request-charge", "0" } };
if (responseHeaders != null)
{
foreach (var responseHeader in responseHeaders)
{
headers[responseHeader.Key] = responseHeader.Value;
}
}
var headersDictionaryType = Type.GetType("Microsoft.Azure.Documents.Collections.DictionaryNameValueCollection, Microsoft.Azure.DocumentDB.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var headersDictionaryInstance = Activator.CreateInstance(headersDictionaryType, headers);
var arguments = new [] { Stream.Null, headersDictionaryInstance, statusCode, null };
var documentServiceResponse = documentServiceResponseType.GetTypeInfo().GetConstructors(flags)[0].Invoke(arguments);
var responseField = typeof(ResourceResponse<T>).GetTypeInfo().GetField("response", flags);
responseField?.SetValue(resourceResponse, documentServiceResponse);
return resourceResponse;
}
You can read more about CosmosDB C# code unit testing here
Thanks @Elfocrash for the reply.
I tried the solution you gave for 2.0.0 version (released 4 days back). But I am getting null value for below Get Type calls
var documentServiceResponseType = Type.GetType("Microsoft.Azure.Documents.DocumentServiceResponse, Microsoft.Azure.DocumentDB.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var headersDictionaryType = Type.GetType("Microsoft.Azure.Documents.Collections.DictionaryNameValueCollection, Microsoft.Azure.DocumentDB.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
I have matched the KeyTOken and version with original dll and it's correct.
Please suggest what am I missing.
Thanks
@joshidp Are you using Microsoft.Azure.DocumentDB or Microsoft.Azure.DocumentDB.Core? if you are using the first one you need to change Microsoft.Azure.DocumentDB.Core
to Microsoft.Azure.DocumentDB
Thanks, I have corrected the assembly name to non-core. It's working fine now.
Thanks a lot :)
Hi,
Any update on this?
is there a in-memory version of IDocumentClient similar to https://github.com/aws-samples/aws-dynamodb-examples/blob/master/src/test/java/com/amazonaws/services/dynamodbv2/local/embedded/DynamoDBEmbeddedTest.java
It will great to have such implementation to test basic flow
I am trying to create a IDocumentClient mock but ran into a problem. While IDocumentQuery is public and allows mocking, IDocumentQueryProvider is internal. We have calls to query.CountAsync(), which is implemented in DocumentQueryable like this:
return ((IDocumentQueryProvider)source.Provider).ExecuteAsync(...
here "source" is our mock implementation of IDocumentQuery but source.Provider is not IDocumentProvider and the mock cannot be completed.
The same problem applies to all other aggregate functions,
@yyanev did you solve this issue? the issue was created in 2017. :(
+1 on this still being an issue, mocking anything that returns a ResourceResponse<T>
is impossible without runtime reflections!
I am trying to create a IDocumentClient mock but ran into a problem. While IDocumentQuery is public and allows mocking, IDocumentQueryProvider is internal. We have calls to query.CountAsync(), which is implemented in DocumentQueryable like this:
return ((IDocumentQueryProvider)source.Provider).ExecuteAsync(...
here "source" is our mock implementation of IDocumentQuery but source.Provider is not IDocumentProvider and the mock cannot be completed.
The same problem applies to all other aggregate functions,