1
votes

I'm trying to write some unit tests for a HttpClient service, and have run into an issue when trying to mock out parts of the code within my GetAccessToken() function.

The service takes in an IHttpClientFactory. From the reading I've done so far it seems that to mock this properly I must mock the IHttpClientFactory, getting it to return a new HttpClient that relies on a mocked HttpMessageHandler, which I have done like this:

var mockHttpClientFactory = new Mock<IHttpClientFactory>();

var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(new HttpResponseMessage
        {
         StatusCode = HttpStatusCode.OK,
         Content = new StringContent("{'name':thecodebuzz,'city':'USA'}"),
        });

var client = new HttpClient(mockHttpMessageHandler.Object);
mockHttpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(client);

_httpClientService = new HttpClientService(mockHttpClientFactory.Object, mockConfig.Object); 

This aspect works and I've been able to call CreateClient() which is working fine. However the function I'm trying to test calls my GetAccessToken() function, which in turn makes a call to client.GetDiscoveryAccessToken(). I'm guessing the GetDiscoveryAccessToken() function needs to be mocked, but I'm unsure how to proceed.

I've tried creating a mock of the client with a mocked method and returning this when CreateClient is called like so:

 var discoDoc = // Mocked disco doc code;

 var client = new Mock<HttpClient>(mockHttpMessageHandler.Object);
 client.Setup(x => x.GetDiscoveryDocumentAsync(It.IsAny<string>())).Returns(discoDoc);

 mockHttpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(client);

 _httpClientService = new HttpClientService(mockHttpClientFactory.Object, mockConfig.Object);

Unfortunately I get this error: "Cannot convert from 'Moq.Mock' to 'System.Net.Http.HttpClient'" on the line where I tell the CreateClient function what to return. I'm guessing this is because I need to be returning a real HttpClient rather than a Mocked one.

Any ideas about where I can go from here?

2

2 Answers

1
votes

You are almost there, For CreateClient, You are returning the Object of Moq, instead of Mocked object. Just need to return mocked object as below.

 var discoDoc = // Mocked disco doc code;

 var client = new Mock<HttpClient>(mockHttpMessageHandler.Object);
 client.Setup(x => x.GetDiscoveryDocumentAsync(It.IsAny<string>())).Returns(discoDoc);

 mockHttpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(client.Object);

 _httpClientService = new HttpClientService(mockHttpClientFactory.Object, mockConfig.Object);

Let me know if this is what you are expecting.

1
votes

Actually there is no way to mock this. The only way to test this, is to do it in a similar fashion as the IdentityModel team did it - using their own implementation of HttpMessageHandler. (NetworkHandler) You can then do something like:

HttpResponseMessage GetDiscoveryResponse()
{
    var wellKnown = File.ReadAllText("openid-configuration.json");
    var response = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(wellKnown)
    };
    return response;
};
var httpClient = new HttpClient(new NetworkHandler(request => {
    if (request.RequestUri.AbsoluteUri.Contains("openid-configuration"))
    {
        return GetDiscoveryResponse();
    }
    throw new NotImplementedException();
}));