Azure / azure-cosmos-dotnet-v2

Contains samples and utilities relating to the Azure Cosmos DB .NET SDK
MIT License
577 stars 837 forks source link

ResourceResponse<Document> Reflection Instantiation Error #589

Closed joshidp closed 6 years ago

joshidp commented 6 years ago

Hi All,

In below issue, we discussed that ResourceResponse can be instantiated using reflection. I am using the code snippet provided by @eanyanwu https://github.com/Azure/azure-cosmosdb-dotnet/issues/342

But I am getting below error

'Constructor on type 'Microsoft.Azure.Documents.DocumentServiceResponse' not found.'

Basically, I want to "mock" ResourceResponse

Please suggest a solution.

Elfocrash commented 6 years ago

You can do that by adding Cosmonaut's TestingExtensions

Here is an 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

joshidp commented 6 years ago

Thanks, it's very helpful