4

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 Answers 2

2

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();
}));
Sign up to request clarification or add additional context in comments.

1 Comment

Not that the base address of the HttpClient must be set and it must match the "issuer" value of the openid-configuration.json file.
1

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 Comment

Thanks! I can't verify that this works as expected because GetDiscoveryDocumentsAsync is causing some kind of issue due to it being an extension method and so can't be used in setup/verification expressions. But I'm guessing that if I were to resolve that issue your answer would solve my problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.