Mocking Fails for Azure Function (IHttpClientFactory) - c#

I am facing issues while mocking the IHttpClientFactory Interface in Azure Function. Here is What am doing, I have trigger, once the message the received I am calling an API to update data. I am using SendAsync Method for that. While writing unit test case, I am not able to mock the client.For testing , I have tried making the get call in the constructor itself, still didn't work.
Function Class
public class UpdateDB
{
private readonly IHttpClientFactory _clientFactory;
private readonly HttpClient _client;
public UpdateDB(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
_client = clientFactory.CreateClient();
_client.GetAsync("");
}
[FunctionName("DB Update")]
public async Task Run([ServiceBusTrigger("topic", "dbupdate", Connection = "connection")]string mySbMsg, ILogger log)
{
var client = _clientFactory.CreateClient();
log.LogInformation($"C# ServiceBus topic trigger function processed message: {mySbMsg}");
DBConvert payload = JsonConvert.DeserializeObject<DBConvert>(mySbMsg);
string jsonContent = JsonConvert.SerializeObject(payload);
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "api/DBU/data");
message.Content = httpContent;
var response = await client.SendAsync(message);
}
}
TestClass
namespace XUnitTestProject1
{
public class DelegatingHandlerStub : DelegatingHandler
{
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub()
{
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc)
{
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return _handlerFunc(request, cancellationToken);
}
}
public class test
{
[Fact]
public async Task Should_Return_Ok()
{
//
Mock<ILogger> _logger = new Mock<ILogger>();
var expected = "Hello World";
var mockFactory = new Mock<IHttpClientFactory>();
var configuration = new HttpConfiguration();
var clientHandlerStub = new DelegatingHandlerStub((request, cancellationToken) =>
{
request.SetConfiguration(configuration);
var response = request.CreateResponse(HttpStatusCode.Accepted);
return Task.FromResult(response);
});
var client = new HttpClient(clientHandlerStub);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
var clientTest = mockFactory.Object.CreateClient();
//Works Here, but not in the instance.
clientTest.GetAsync("");
IHttpClientFactory factory = mockFactory.Object;
var service = new UpdateDB(factory);
await service.Run("", _logger.Object);
}
}
}
I have followed the sample here. How to mock the new HttpClientFactory in .NET Core 2.1 using Moq

For mocking/intercepting HttpClient usage, I would recommend you to use mockhttp
Your test would then be:
class Test {
private readonly Mock<IHttpClientFactory> httpClientFactory;
private readonly MockHttpMessageHandler handler;
constructor(){
this.handler = new MockHttpMessageHandler();
this.httpClientFactory = new Mock<IHttpClientFactory>();
this.httpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>()))
.Returns(handler.ToHttpClient());
}
[Fact]
public async Task Test(){
// Arrange
this.handler.Expect("api/DBU/data")
.Respond(HttpStatusCode.Ok);
var sut = this.CreateSut();
// Act
await sut.Run(...);
// Assert
this.handler.VerifyNoOutstandingExpectation();
}
private UpdateDB CreateSut() => new UpdateDB(this.httpClientFactory.Object);
}
You can further configure how the HTTP request expectation should behave, but for that you should read a bit the documentation of mockhttp

Related

mock HttpClient SendAsync method

My code is using HttpClient in order to retrieve some data
HttpClient client = new HttpClient
{
BaseAddress = new Uri("myurl.com"),
};
var msg = new HttpRequestMessage(HttpMethod.Get, "myendpoint");
var res = await client.SendAsync(msg);
how can I mock this SendAsync method on HttpClient and inject it inside .net core ServiceCollection?
I tried to mock like this
var mockFactory = 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);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
but how to inject this mockFactory into ServiceCollection? Or maybe there is some easier or different way around?
Instead of mocking the HTTP call, why not encapsulate it? Then you can mock the encapsulation/abstraction.
For example:
interface IClient
{
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);
}
class HttpClientAdapter : IClient
{
readonly HttpClient _client;
public HttpClientAdapter(HttpClient client)
{
_client = client;
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) => _client.SendAsync(request, cancellationToken);
}
Make your code depend on the IClient interface. During normal usage you would use a real HttpClient in the HttpClientAdapter implementation. And for tests you could mock the IClient.
Note it might be more useful to you to make the abstraction a little higher level than this. For example, if you're expecting to parse a JSON string from HTTP responses into some DataObject then maybe your IClient interface should look more like this:
class DataObject
{
public string Name { get; set; }
public string City { get; set; }
}
interface IClient
{
Task<DataObject> GetAsync(CancellationToken cancellationToken = default);
}
public class ClientImplementation : IClient
{
readonly HttpClient _client;
public ClientImplementation(HttpClient client)
{
_client = client;
}
public async Task<DataObject> GetAsync(CancellationToken cancellationToken = default)
{
var response = await _client.SendAsync(...);
var dataObject = new DataObject();
// parse the response into the data object
return dataObject;
}
}
The advantage of drawing the line here is your tests will have less work to do. Your mock code won't have to set up HttpResponseMessage objects, for example.
Where you choose to draw the boundaries for your abstractions is totally up to you. But the key takeaway is: once your code depends on a small interface then it's easy to mock that interface and test your code.
If you really need to mock the HttpClient itself, take a look at this lib:
https://github.com/richardszalay/mockhttp
From the docs:
var mockHttp = new MockHttpMessageHandler();
// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON
// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();
var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;
var json = await response.Content.ReadAsStringAsync();
// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}

.net services.AddHttpClient Automatic Access Token Handling

I am trying to write a Blazor app that uses client secret credentials to get an access token for the API. I wanted to encapsulate it in such a way that it handles the token fetching and refreshing behind the scenes. To achieve this, I created the following inherited class which uses IdentityModel Nuget package:
public class MPSHttpClient : HttpClient
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger Logger;
public MPSHttpClient(IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
public async Task<bool> RefreshTokens()
{
if (Tokens.RefreshToken == null)
return false;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
RefreshToken = Tokens.RefreshToken
});
Logger.LogInformation("Refresh Token Result {0}", result.IsError);
if (result.IsError)
{
Logger.LogError("Error: {0)", result.ErrorDescription);
return false;
}
Tokens.RefreshToken = result.RefreshToken;
Tokens.AccessToken = result.AccessToken;
Logger.LogInformation("Access Token: {0}", result.AccessToken);
Logger.LogInformation("Refresh Token: {0}" , result.RefreshToken);
return true;
}
public async Task<bool> CheckTokens()
{
if (await RefreshTokens())
return true;
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
public new async Task<HttpResponseMessage> GetAsync(string requestUri)
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
var response = await base.GetAsync(requestUri);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Tokens.AccessToken);
response = await base.GetAsync(requestUri);
}
}
return response;
}
}
The idea is to keep from having to write a bunch of redundant code to try the API, then request/refresh the token if you are unauthorized. I tried it at first using extension methods to HttpClient, but there was no good way to inject the Configuration into a static class.
So my Service code is written as this:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly MPSHttpClient _httpClient;
public EngineListService(MPSHttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
Everything compiles great. In my Startup, I have the following code:
services.AddScoped<TokenProvider>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Just to be complete, Token Provider looks like this:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
When I run the App, it complains that it can't find a suitable constructor for EngineListService in the call to services.AddHttpClient. Is there a way to pass AddHttpClient an actual instance of the IEngineListService. Any other way I might be able to achieve this?
Thanks,
Jim
I think that EngineListService should not be registered as a HttpClient in services and instead you should register MPSHttpClient.
This follows the "Typed Client" example in the documentation and uses IHttpClientFactory behind the scenes.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests#typed-clients
When you use services.AddHttpClient the constructor needs a HttpClient parameter. That is how the HttpClientFactory initializes the HttpClient and then passes it into your service ready to go.
You can change your MPSHttpClient to not inherit HttpClient and instead add a HttpClient parameter to the constructor. You could also have it implement an interface like IMPSHttpClient
public class MPSHttpClient
{
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger logger)
{
HttpClient = httpClient;
Configuration = configuration;
Tokens = tokens;
Logger = logger;
}
}
You must remove these lines from MPSHttpClient and use the injected client.
// remove this
var client = new HttpClient();
In Startup add
services.AddHttpClient<MPSHttpClient>(client =>
{
// add any configuration
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
});
Change EngineListService to a normal service registration as it is not a HttpClient
services.AddScoped<IEngineListService, EngineListService>()
Special thanks to #pinkfloydx33 for helping me solve this. This link that he shared https://blog.joaograssi.com/typed-httpclient-with-messagehandler-getting-accesstokens-from-identityserver/ was everything I needed. The trick was that there exists a class called DelegatingHandler that you can inherit and override the OnSendAsync method and do all of your token-checking there before sending it to the final HttpHandler. So my new MPSHttpClient class is as so:
public class MPSHttpClient : DelegatingHandler
{
private readonly IConfiguration Configuration;
private readonly TokenProvider Tokens;
private readonly ILogger<MPSHttpClient> Logger;
private readonly HttpClient client;
public MPSHttpClient(HttpClient httpClient, IConfiguration configuration, TokenProvider tokens, ILogger<MPSHttpClient> logger)
{
Configuration = configuration;
Tokens = tokens;
Logger = logger;
client = httpClient;
}
public async Task<bool> CheckTokens()
{
var disco = await client.GetDiscoveryDocumentAsync(Configuration["Settings:Authority"]);
if (disco.IsError) throw new Exception(disco.Error);
var result = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = Configuration["Settings:ClientID"],
ClientSecret = Configuration["Settings:ClientSecret"]
});
if (result.IsError)
{
//Log("Error: " + result.Error);
return false;
}
Tokens.AccessToken = result.AccessToken;
Tokens.RefreshToken = result.RefreshToken;
return true;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBearerToken(Tokens.AccessToken);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
if (await CheckTokens())
{
request.SetBearerToken(Tokens.AccessToken);
response = await base.SendAsync(request, cancellationToken);
}
}
return response;
}
}
The big changes here are the inheritance and I used DI to obtain the HttpClient much like #Rosco mentioned. I had tried to override OnGetAsync in my original version. When inheriting from DelegatingHandler, all you have to override is OnSendAsync. This will handle all of your get, put, post, and deletes from your HttpContext all in one method.
My EngineList Service is written as if there were no tokens to be considered, which was my original goal:
public interface IEngineListService
{
Task<IEnumerable<EngineList>> GetEngineList();
}
public class EngineListService : IEngineListService
{
private readonly HttpClient _httpClient;
public EngineListService(HttpClient httpClient)
{
_httpClient = httpClient;
}
async Task<IEnumerable<EngineList>> IEngineListService.GetEngineList()
{
return await JsonSerializer.DeserializeAsync<IEnumerable<EngineList>>
(await _httpClient.GetStreamAsync($"api/EngineLists"), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
}
The Token Provider stayed the same. I plan to add expirations and such to it, but it works as is:
public class TokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
The ConfigureServices code changed just a bit:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<TokenProvider>();
services.AddTransient<MPSHttpClient>();
services.AddHttpClient<IEngineListService, EngineListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
...
}
You instantiate MPSHttpClient as Transient, then reference it with the AddHttpMessageHandler call attached to the AddHttpClient call. I know this is different than how others implement HttpClients, but I learned this method of creating client services from a Pluralsight video and have been using it for everything. I create a separate Service for each entity in the database. If say I wanted to do tires, I would add the following to ConfigureServices:
services.AddHttpClient<ITireListService, TireListService>(client =>
{
client.BaseAddress = new Uri(Configuration["Settings:ApiAddress"]);
}).AddHttpMessageHandler<MPSHttpClient>();
It will use the same DelegatingHandler so I can just keep adding services for each entity type while no longer worrying about tokens. Thanks to everyone that responded.
Thanks,
Jim

Mocking IHttpClientFactory - xUnit C#

I was trying to build a generic HTTP service in my project (c# with .net core 2.1), and I have done it as per the below snippet HttpService.
I also started using it by calling it from my business logic class which uses this generic PostAsync method to post an HTTP call to a 3rd party with a content in body. It works perfectly.
But, when I tried to test it, I failed!
Actually when I tried debugging (testing mode), I get null response when the debugger comes to this line var result = await _httpService.PostAsync("https://test.com/api", content); in business class Processor even with fake objects and mocks, although it works normally in debugging mode without testing/mocking.
HTTP service:
public interface IHttpService
{
Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content);
}
public class HttpService : IHttpService
{
private readonly IHttpClientFactory _httpClientFactory;
public HttpService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content)
{
var httpClient = _httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(3);
var response = await httpClient.PostAsync(requestUri, content).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return response;
}
}
Business class:
public class Processor : IProcessor
{
private readonly IHttpService _httpService;
public Processor() { }
public Processor(IHttpService httpService, IAppSettings appSettings)
{
_httpService = httpService;
}
public async Task<HttpResponseMessage> PostToVendor(Order order)
{
// Building content
var json = JsonConvert.SerializeObject(order, Formatting.Indented);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// HTTP POST
var result = await _httpService.PostAsync("https://test.com/api", content); // returns null during the test without stepping into the method PostAsyn itself
return result;
}
}
Test class:
public class MyTests
{
private readonly Mock<IHttpService> _fakeHttpMessageHandler;
private readonly IProcessor _processor; // contains business logic
private readonly Fixture _fixture = new Fixture();
public FunctionTest()
{
_fakeHttpMessageHandler = new Mock<IHttpService>();
_processor = new Processor(_fakeHttpMessageHandler.Object);
}
[Fact]
public async Task Post_To_Vendor_Should_Return_Valid_Response()
{
var fakeHttpResponseMessage = new Mock<HttpResponseMessage>(MockBehavior.Loose, new object[] { HttpStatusCode.OK });
var responseModel = new ResponseModel
{
success = true,
uuid = Guid.NewGuid().ToString()
};
fakeHttpResponseMessage.Object.Content = new StringContent(JsonConvert.SerializeObject(responseModel), Encoding.UTF8, "application/json");
var fakeContent = _fixture.Build<DTO>().Create(); // DTO is the body which gonna be sent to the API
var content = new StringContent(JsonConvert.SerializeObject(fakeContent), Encoding.UTF8, "application/json");
_fakeHttpMessageHandler.Setup(x => x.PostAsync(It.IsAny<string>(), content))
.Returns(Task.FromResult(fakeHttpResponseMessage.Object));
var res = _processor.PostToVendor(fakeContent).Result;
Assert.NotNull(res.Content);
var actual = JsonConvert.SerializeObject(responseModel);
var expected = await res.Content.ReadAsStringAsync();
Assert.Equal(expected, actual);
}
}
Your problem is in mock set up:
_fakeHttpMessageHandler.Setup(x => x.PostAsync(It.IsAny<string>(), content))
.Returns(Task.FromResult(fakeHttpResponseMessage.Object));
Second parameter for PostAsync method expected to be content, but since StringContent is a reference type, content you setup in mock is different from content you creating in processor. If you change it to next one, it should work as you expect:
_fakeHttpMessageHandler.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<StringContent>()))
.Returns(Task.FromResult(fakeHttpResponseMessage.Object));
P.S. null response to PostAsync means that method has default setup, which means that it will return default value

Method Not Found when using MOQ in Unit Test

I am having trouble setting up a unit test where I need to Moq the HttpClient. In my code I have a decorator for the HttpClient which follows an interface.
public class WHttpClient: IWHttpClient{
HttpClient _client = new HttpClient();
...
public async Task<HttpReponseMessage> PostAsJsonAsync<T>(string url, T content)
{
//Do Something
return await _client.PostAsJsonAsync(url, content);
}
...
}
public interface IWHttpClient{
HttpRequestHeaders DefaultRequestHeaders {get;}
Task<HttpResponseMessage> PostAsXmlAsync<T>(string url, T content);
Task<HttpResponseMessage> PostAsJsonAsync<T>(string url, T content);
Task<HttpResponseMessage> PostAsync<T>(string url, T content);
Task<HttpResponseMessage> GetAsync(string url);
Task<T> GetAsync<T>(string url);
Task<T> ReadAsAsync<T>(HttpResponseMessage response);
T Read<T>(HttpResponseMessage response);
}
[TestClass]
public class UnitTest1
{
private class WorkClass
{
private IWHttpClient _client;
public WorkClass(IWHttpClient client)
{
_client = client;
}
public void DoWork()
{
var url = "DUMMY";
var content = new ObjectToSerialize();
Task.Run(() => _client.PostAsJsonAsync(url, content));
}
}
public class ObjectToSerialize
{
}
[TestMethod]
public void TestMethod1()
{
Mock<IWHttpClient> _webClientMock = new Mock<IWHttpClient>(MockBehavior.Strict);
var url = "DUMMY";
var content = new ObjectToSerialize();
_webClientMock.Setup(x => x.PostAsJsonAsync(url, It.IsAny<ObjectToSerialize>())).Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));
var myClassToTest = new WorkClass(_webClientMock.Object);
myClassToTest.DoWork();
}
}
It successfully builds, but when I run the test, it gives me the exception:
System.MissingMethodException: Method not found: 'System.Threading.Tasks.Task'1 SomeNamespace.IWHttpClient.PostAsJsonAsync(System.String, !!0)'.
I've spent hours trying to figure out why I get this exception when I run the test. I have performed a clean and rebuild of my solution and yet it still appears as well as replacing the inputs in the mocksetup with:
(It.IsAny<string>(), It.IsAny<object>())
Does anyone have an idea what's wrong?
Results from running:
The following minimal example was just to try and reproduce your problem as well as demonstrate how to exercise tests like this.
[TestClass]
public class MyTestClass {
private class WorkClass {
private IWHttpClient _client;
public WorkClass(IWHttpClient client) {
_client = client;
}
public async Task DoWork() {
var url = "DUMMY";
var content = new ObjectToSerialize();
var response = await _client.PostAsJsonAsync(url, content);
}
}
public class ObjectToSerialize {
}
[TestMethod]
public async Task MyTestMethod() {
//Arrange
var expectedResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
var _webClientMock = new Mock<IWHttpClient>(MockBehavior.Strict);
_webClientMock
.Setup(_ => _.PostAsJsonAsync(It.IsAny<string>(), It.IsAny<ObjectToSerialize>()))
.ReturnsAsync(expectedResponse)
.Verifiable();
var myClassToTest = new WorkClass(_webClientMock.Object);
//Act
await myClassToTest.DoWork();
//Assert
_webClientMock.Verify();
}
}
When exercised the test behaved as expected and passed. Even when the setup was changed to
.Setup(_ => _.PostAsJsonAsync(It.IsAny<string>(), It.IsAny<object>()))
Review and compare to your current test to help identify where possible mistakes may have been made.

How can I use FakeItEasy with HttpClient, in a unit test?

I'm trying to figure out how to use FakeItEasy with the HttpClient, given the following code:
public Foo(string key, HttpClient httpClient = null)
{ .. }
public void DoGet()
{
....
if (_httpClient == null)
{
_httpClient = new HttpClient();
}
var response = _httpClient.GetAsync("user/1);
}
public void DoPost(foo Foo)
{
if (_httpClient == null)
{
_httpClient = new HttpClient();
}
var formData = new Dictionary<string, string>
{
{"Name", "Joe smith"},
{"Age", "40"}
};
var response = _httpClient.PostAsync("user",
new FormUrlEncodedContent(formData));
}
So i'm not sure how to use FakeItEasy, to fake out the HttpClient's GetAsync and PostAsync methods.
production code will not pass in the HttpClient, but the unit test will pass in the fake instance, made by FakeItEasy.
eg.
[Fact]
public void GivenBlah_DoGet_DoesSomething()
{
// Arrange.
var httpClient A.Fake<HttpClient>(); // <-- need help here.
var foo = new Foo("aa", httpClient);
// Act.
foo.DoGet();
// Assert....
}
UPDATE:
I grok that FiE (and most mocking packages) works on interfaces or virtual methods. So for this question, lets just prentend that the GetAsync and PostAsync methods are virtual ... please :)
Here's my (more or less) general purpose FakeHttpMessageHandler.
public class FakeHttpMessageHandler : HttpMessageHandler
{
private HttpResponseMessage _response;
public static HttpMessageHandler GetHttpMessageHandler( string content, HttpStatusCode httpStatusCode )
{
var memStream = new MemoryStream();
var sw = new StreamWriter( memStream );
sw.Write( content );
sw.Flush();
memStream.Position = 0;
var httpContent = new StreamContent( memStream );
var response = new HttpResponseMessage()
{
StatusCode = httpStatusCode,
Content = httpContent
};
var messageHandler = new FakeHttpMessageHandler( response );
return messageHandler;
}
public FakeHttpMessageHandler( HttpResponseMessage response )
{
_response = response;
}
protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken )
{
var tcs = new TaskCompletionSource<HttpResponseMessage>();
tcs.SetResult( _response );
return tcs.Task;
}
}
Here is an example of it being used from one of my tests that expects some JSON as a return value.
const string json = "{\"success\": true}";
var messageHandler = FakeHttpMessageHandler.GetHttpMessageHandler(
json, HttpStatusCode.BadRequest );
var httpClient = new HttpClient( messageHandler );
You would now inject httpClient into your class under test (using whatever injection mechanism you prefer) and when GetAsync is called your messageHandler will spit back the result you told it to.
You could also create an AbstractHandler on which you can intercept a public abstract method. For instance:
public abstract class AbstractHandler : HttpClientHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(SendAsync(request.Method, request.RequestUri.AbsoluteUri));
}
public abstract HttpResponseMessage SendAsync(HttpMethod method, string url);
}
Then you can intercept calls to the AbstractHandler.SendAsync(HttpMethod method, string url) like:
// Arrange
var httpMessageHandler = A.Fake<AbstractHandler>(options => options.CallsBaseMethods());
A.CallTo(() => httpMessageHandler.SendAsync(A<HttpMethod>._, A<string>._)).Returns(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Result")});
var httpClient = new HttpClient(httpMessageHandler);
// Act
var result = await httpClient.GetAsync("https://google.com/");
// Assert
Assert.Equal("Result", await result.Content.ReadAsStringAsync());
A.CallTo(() => httpMessageHandler.SendAsync(A<HttpMethod>._, "https://google.com/")).MustHaveHappenedOnceExactly();
More information can be found on this blog: https://www.meziantou.net/mocking-an-httpclient-using-an-httpclienthandler.htm
I did something like this when I needed to interact with the Gravatar service. I tried to use fakes/mocks but found it was impossible with HttpClient. Instead, I came up with a custom HttpMessageHandler class that lets me pre-load the expected response, along these lines:
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Tigra.Gravatar.LogFetcher.Specifications
{
/// <summary>
/// Class LoggingHttpMessageHandler.
/// Provides a fake HttpMessageHandler that can be injected into HttpClient.
/// The class requires a ready-made response message to be passed in the constructor,
/// which is simply returned when requested. Additionally, the web request is logged in the
/// RequestMessage property for later examination.
/// </summary>
public class LoggingHttpMessageHandler : DelegatingHandler
{
internal HttpResponseMessage ResponseMessage { get; private set; }
internal HttpRequestMessage RequestMessage { get; private set; }
public LoggingHttpMessageHandler(HttpResponseMessage responseMessage)
{
ResponseMessage = responseMessage;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
RequestMessage = request;
return Task.FromResult(ResponseMessage);
}
}
}
Then my test context setup goes something like this:
public class with_fake_gravatar_web_service
{
Establish context = () =>
{
MessageHandler = new LoggingHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK));
GravatarClient = new HttpClient(MessageHandler);
Filesystem = A.Fake<FakeFileSystemWrapper>();
Fetcher = new GravatarFetcher(Committers, GravatarClient, Filesystem);
};
protected static LoggingHttpMessageHandler MessageHandler;
protected static HttpClient GravatarClient;
protected static FakeFileSystemWrapper Filesystem;
}
Then, here's an example of a test (specification) that uses it:
[Subject(typeof(GravatarFetcher), "Web service")]
public class when_fetching_imagaes_from_gravatar_web_service : with_fake_gravatar_web_service
{
Because of = () =>
{
var result = Fetcher.FetchGravatars(#"c:\"); // This makes the web request
Task.WaitAll(result.ToArray());
//"http://www.gravatar.com/avatar/".md5_hex(lc $email)."?d=404&size=".$size;
UriPath = MessageHandler.RequestMessage.RequestUri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
};
It should_make_request_from_gravatar_dot_com =
() => MessageHandler.RequestMessage.RequestUri.Host.ShouldEqual("www.gravatar.com");
It should_make_a_get_request = () => MessageHandler.RequestMessage.Method.ShouldEqual(HttpMethod.Get);
// see https://en.gravatar.com/site/check/tim#tigranetworks.co.uk
It should_request_the_gravatar_hash_for_tim_long =
() => UriPath.ShouldStartWith("avatar/df0478426c0e47cc5e557d5391e5255d");
static string UriPath;
}
You can see the full source at http://stash.teamserver.tigranetworks.co.uk/users/timlong/repos/tigra.gravatar.logfetcher/browse
FakeItEasy, like most mocking libraries, does not create proxies for non-abstract components. In the case of HttpClient, the GetAsync and PostAsync methods are neither virtual nor abstract, so you can't create stub implementations of them directly. See https://github.com/FakeItEasy/FakeItEasy/wiki/What-can-be-faked.
In this case, you need a different abstraction as a dependency - one which HttpClient can fulfill, but so could other implementations, including mocks/stubs.
This isn't answering your question directly, but I wrote a library a while back that provides an API for stubbing out requests/responses. It's pretty flexible, and supports ordered/unordered matching as well as a customisable fallback system for unmatched requests.
It's available on GitHub here: https://github.com/richardszalay/mockhttp

Categories