How to mock HttpClient request in C# - c#

I have some code like this and I tried using HttpMock and MockHttp but it was of no use is there any otherway to mock the HttpClient Request? I can't edit my code and implement an interface to write UnitTestCases as this would involve modifying lot of other code!
public GetData(string url,string data)
{
//does some logic and adds the parameter to URL
string finalurl=url+"?data=data";
using (var client = new HttpClient(new TokenHandler()))
{
var response = await client.GetAsync(finalurl);
var responsedata = await
response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.OK)
{
responsefinal = responsedata;
}
}
return responsefinal;
}

If you want to avoid running another server altogether, you can use the overloads of the HttpClient constructor. This would require some minor changes to your code (but no need for any interface over your client).
The HttpClient constructor takes a an instance of HttpMessageHandler. You can create a mock implementation of this that will enable you to inspect the request you get from the client and return an HttpResponseMessage, allowing you to mock a variety of scenarios:
public class MockMessageHandler: HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
// Capture request properties, return response
}
}
You can then pass in a factory method (a function delegate if necessary) to generate instances of your message handler as required:
public GetData(String url, String data, Func<HttpMessageHandler> handlerFactory)
And then use it in the HttpClientConstructor:
using (var client = new HttpClient(handlerFactory()))

Related

How to Mock HttpClient using Substitute

I am trying to use NSubstitute to mock HttpClient. Here's the code:
public static HttpClient GetHttpClient(bool isSucess = true, string methodType = "GET")
{
var mockIHttpMessageHandler = Substitute.For<IMockHttpMessageHandler>();
var mockHttpMessageHandler = Substitute.For<MockHttpMessageHandler>(mockIHttpMessageHandler);
var httpResponse = Substitute.For<HttpResponseMessage>();
httpResponse.Content = new StringContent("\"test\"");
if (isSucess)
httpResponse.StatusCode = HttpStatusCode.OK;
else
httpResponse.StatusCode = HttpStatusCode.NotFound;
var mockHttpClient = Substitute.For<HttpClient>(mockHttpMessageHandler);
mockHttpClient.BaseAddress = new Uri("http://localhost");
if(methodType != "POST"){
mockHttpClient.GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);
}
return mockHttpClient;
}
However, I got an error at this line:
mockHttpClient.GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);
And the error is
NSubstitute.Exceptions.RedundantArgumentMatcherException: 'Some
argument specifications (e.g. Arg.Is, Arg.Any) were left over after
the last call.
This is often caused by using an argument spec with a call to a member
NSubstitute does not handle (such as a non-virtual member or a call to
an instance which is not a substitute), or for a purpose other than
specifying a call (such as using an arg spec as a return value). For
example:
var sub = Substitute.For<SomeClass>();
var realType = new MyRealType(sub);
// INCORRECT, arg spec used on realType, not a substitute:
realType.SomeMethod(Arg.Any<int>()).Returns(2);
// INCORRECT, arg spec used as a return value, not to specify a call:
sub.VirtualMethod(2).Returns(Arg.Any<int>());
// INCORRECT, arg spec used with a non-virtual method:
sub.NonVirtualMethod(Arg.Any<int>()).Returns(2);
// CORRECT, arg spec used to specify virtual call on a substitute:
sub.VirtualMethod(Arg.Any<int>()).Returns(2);
To fix this make sure you only use argument specifications with calls
to substitutes. If your substitute is a class, make sure the member is
virtual.
Another possible cause is that the argument spec type does not match
the actual argument type, but code compiles due to an implicit cast.
For example, Arg.Any() was used, but Arg.Any() was
required.
NOTE: the cause of this exception can be in a previously executed
test. Use the diagnostics below to see the types of any redundant arg
specs, then work out where they are being created.
Diagnostic information:
Remaining (non-bound) argument specifications:
any Uri
All argument specifications:
any Uri
Are they suggesting I need to change the getAsync method? There's no virtual method for GetAsync
Edit:
I have also tried to remove NSubstitute for HttpClient as follows, but I still got the same error:
public static HttpClient GetHttpClient(bool isSucess = true, string methodType = "GET")
{
var mockIHttpMessageHandler = Substitute.For<IMockHttpMessageHandler>();
var mockHttpMessageHandler = Substitute.For<MockHttpMessageHandler>(mockIHttpMessageHandler);
var httpResponse = Substitute.For<HttpResponseMessage>();
httpResponse.Content = new StringContent("\"test\"");
if (isSucess)
httpResponse.StatusCode = HttpStatusCode.OK;
else
httpResponse.StatusCode = HttpStatusCode.NotFound;
var httpClient = new HttpClient(mockHttpMessageHandler);
httpClient = new Uri("http://localhost");
if(methodType != "POST"){
httpClient .GetAsync(Arg.Any<Uri>()).ReturnsForAnyArgs(httpResponse);
}
return httpClient
}
I appreciate this is an old(ish) question, but it's at the top of the Google Search results for "c# mock httpclient using nsubstitute" today, so I figured an answer would be useful.
First we need to create a mock implemenation of HttpMessageHandler. As you can see, we're overriding the protected SendAsync() method and exposing it's body via our public Send() method.
public class MockHttpMessageHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Send(request, cancellationToken);
}
public virtual Task<HttpResponseMessage> Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
Next we need to set up our mocks. Note that I'm using Substitute.ForPartsOf<T> instead of Substitute.For<T>.
var mockHttpMessageHandler = Substitute.ForPartsOf<MockHttpMessageHandler>();
var httpClient = new HttpClient(mockHttpMessageHandler);
Finally, we can now use NSubstitute to intercept the call to Send() on our handler, which is called by the HttpClient for every request, and return our mocked HttpResponseMessage back via the client.
var mockResponse = new HttpResponseMessage(HttpStatusCode.OK);
mockHttpMessageHandler.Send(Arg.Any<AnyHttpRequestMessage>(), Arg.Any<CancellationToken>())
.Returns(mockResponse);
var result = await httpClient.GetAsync<string>("https://tempuri.org");
Edit for .NET 6
As .NET 6 introduces a protected virtual Send() method to the HttpMessageHandler class (which will also need overriding if you're using the synchronous HttpClient calls), some modifications are required to our MockHttpMessageHandler:
public class MockHttpMessageHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(MockSend(request, cancellationToken));
}
protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
return MockSend(request, cancellationToken);
}
public virtual HttpResponseMessage MockSend(HttpRequestMessage request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}

How to do integration testing on an external API with ASP.NET Core

I'm trying to do some integration tests on an external API. Most of the guides I find online are about testing the ASP.NET web api, but there's not much to find about external API's. I want to test a GET request on this API and confirm if it passes by checking if the status code is OK. However this test is not passing and im wondering if i'm doing this correctly. Currently it's giving me a status code 404(Not found).
I'm using xUnit together with Microsoft.AspNetCore.TestHost How would you suggest me to test external API's?
private readonly HttpClient _client;
public DevicesApiTests()
{
var server = new TestServer(new WebHostBuilder()
.UseEnvironment("Development")
.UseStartup<Startup>());
_client = server.CreateClient();
}
[Theory]
[InlineData("GET")]
public async Task GetAllDevicesFromPRTG(string method)
{
//Arrange
var request = new HttpRequestMessage(new HttpMethod(method), "https://prtg.nl/api/content=Group,Device,Status");
//Act
var response = await _client.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Edit
The API call which im trying to test looks as follows, and is working properly
private readonly DbContext _dbContext;
private readonly IDevicesRepository _devicesRepository;
public DevicesAPIController(DbContext dbContext, IDevicesRepository devicesRepository)
{
_dbContext = dbContext;
_devicesRepository = devicesRepository;
}
[HttpPost("PostLiveDevicesToDatabase")]
public async Task<IActionResult> PostLiveDevicesToDatabase()
{
try
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
using (var response = await httpClient
.GetAsync(
"https://prtg.nl/api/content=Group,Device,Status")
)
{
string apiResponse = await response.Content.ReadAsStringAsync();
var dataDeserialized = JsonConvert.DeserializeObject<Devices>(apiResponse);
devicesList.AddRange(dataDeserialized.devices);
foreach (DevicesData device in devicesList)
{
_dbContext.Devices.Add(device);
devicesAdded.Add(device);
_dbContext.SaveChanges();
}
}
}
}
catch
{
return BadRequest();
}
}
I would like to propose an alternative solution which involves changing the design of the code to be tested.
The currently shown test-case is coupled to the external API and tests its ability to respond 200 OK rather than your code (i.e., your code isn't referenced at all). This also means that if a connection can't be established to the server (e.g., could be an isolated build agent in a CI/CD pipeline or just a flaky café WIFI) the test fails for another reason than what is asserted.
I would propose to extract the HttpClient, and its configuration that is specific to the API, into an abstraction as you have done with the IDevicesRepository (although it's not used in the example). This allows you to substitute the response from the API and only test your code. The substitutions could explore edge-cases such as the connection down, empty response, malformed response, external server error etc. That way you can exercise more failure-paths in your code and keep the test decoupled from the external API.
The actual substitution of the abstraction would be done in the "arrange" phase of the test. You can use the Moq NuGet package for this.
Update
To provide an example of using Moq to simulate an empty API response consider a hypothetical abstraction such as:
public interface IDeviceLoader
{
public IEnumerable<DeviceDto> Get();
}
public class DeviceDto
{
// Properties here...
}
Keep in mind the example abstraction isn't asynchronous, which could be considered best practices as you are invoking I/O (i.e., the network). I skipped it to keep it simple. See Moq documentation on how to handle async methods.
To mock the response the body of the test case could be:
[Fact]
public async Task CheckEndpointHandlesEmptyApiResponse()
{
// How you get access to the database context and device repository is up to you.
var dbContext = ...
var deviceRepository = ...
//Arrange
var apiMock = new Mock<IDeviceLoader>();
apiMock.Setup(loader => loader.Get()).Returns(Enumerable.Empty<DeviceDto>());
var controller = new DevicesAPIController(dbContext, deviceRepository, apiMock.Object);
//Act
var actionResponse = controller.PostLiveDevicesToDatabase();
// Assert
// Check the expected HTTP result here...
}
Do check the Moq documentation on their repository (linked above) for more examples.
The base address of test server is localhost. TestServer is meant for in-memory integration tests. The client created via TestServer.CreateClient() will create an instance of HttpClient that uses an internal message handler to manage requests specific you your API.
If you are trying to access an external URL by calling the test server. You will get 404 by design.
If https://prtg.nl/api/content is not local to your API and is the actual external link you want to access then use an independent HttpClient
//...
private static readonly HttpClient _client;
static DevicesApiTests() {
_client = new HttpClient();
}
[Theory]
[InlineData("GET")]
public async Task GetAllDevicesFromPRTG(string method) {
//Arrange
var request = new HttpRequestMessage(new HttpMethod(method), "https://prtg.nl/api/content=Group,Device,Status");
//Act
var response = await _client.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
//...
If this is meant to be an end to end via your api then you need to call the local API end point which is dependent on the target controller and action
The example in accepted solution is not an integration test, it's unit test. While it's usable in simple scenarios, I wouldn't recommend you to test controllers directly. On integration test level, controller is an implementation detail of your application. Testing implementation details is considered a bad practice. It makes your tests more flaky and less maintainable.
Instead, you should test your API directly using WebApplicationFactory from Microsoft.AspNetCore.Mvc.Testing package.
https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests
Here is how I would do it
Implementation
Add typed client wrapper around HttpClient
public class DeviceItemDto
{
// some fields
}
public interface IDevicesClient
{
Task<DeviceItemDto[]?> GetDevicesAsync(CancellationToken cancellationToken);
}
public class DevicesClient : IDevicesClient
{
private readonly HttpClient _client;
public DevicesClient(HttpClient client)
{
_client = client;
}
public Task<DeviceItemDto[]?> GetDevicesAsync(CancellationToken cancellationToken)
{
return _client.GetFromJsonAsync<DeviceItemDto[]>("/api/content=Group,Device,Status", cancellationToken);
}
}
Register your typed client in DI
public static class DependencyInjectionExtensions
{
public static IHttpClientBuilder AddDevicesClient(this IServiceCollection services)
{
return services.AddHttpClient<IDevicesClient, DevicesClient>(client =>
{
client.BaseAddress = new Uri("https://prtg.nl");
});
}
}
// Use it in Startup.cs
services.AddDevicesClient();
Use typed client in your controller
private readonly IDevicesClient _devicesClient;
public DevicesController(IDevicesClient devicesClient)
{
_devicesClient = devicesClient;
}
[HttpGet("save")]
public async Task<IActionResult> PostLiveDevicesToDatabase(CancellationToken cancellationToken)
{
var devices = await _devicesClient.GetDevicesAsync(cancellationToken);
// save to database code
// you can return saved devices, or their ids
return Ok(devices);
}
Tests
Add fake HttpMessageHandler for mocking HTTP responses
public class FakeHttpMessageHandler : HttpMessageHandler
{
private HttpStatusCode _statusCode = HttpStatusCode.NotFound;
private HttpContent? _responseContent;
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(_statusCode)
{
Content = _responseContent
};
return Task.FromResult(response);
}
public FakeHttpMessageHandler WithDevicesResponse(IEnumerable<DeviceItemDto> devices)
{
_statusCode = HttpStatusCode.OK;
_responseContent = new StringContent(JsonSerializer.Serialize(devices));
return this;
}
}
Add custom WebApplicationFactory
internal class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
// Use the same method as in implementation
services.AddDevicesClient()
// Replaces the default handler with mocked one to avoid calling real API in tests
.ConfigurePrimaryHttpMessageHandler(() => new FakeHttpMessageHandler());
});
}
// Use this method in your tests to setup specific responses
public WebApplicationFactory<Program> UseFakeDevicesClient(
Func<FakeHttpMessageHandler, FakeHttpMessageHandler> configureHandler)
{
var handler = configureHandler.Invoke(new FakeHttpMessageHandler());
return WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddDevicesClient().ConfigurePrimaryHttpMessageHandler(() => handler);
});
});
}
}
Test will look like this:
public class GetDevicesTests
{
private readonly CustomWebApplicationFactory _factory = new();
[Fact]
public async void Saves_all_devices_from_external_resource()
{
var devicesFromExternalResource => new[]
{
// setup some test data
}
var client = _factory
.UseFakeDevicesClient(_ => _.WithDevicesResponse(devicesFromExternalResource))
.CreateClient();
var response = await client.PostAsync("/devices/save", CancellationToken.None);
var devices = await response.Content.ReadFromJsonAsync<DeviceItemDto[]>();
response.StatusCode.Should().Be(200);
devices.Should().BeEquivalentTo(devicesFromExternalResource);
}
}
Code example
You can customise CustomWebApplicationFactory and FakeHttpMessageHandler according to your test cases, but I hope the idea is clear

Using MockHttpClient nuget package in VS 2017

I am trying to test an adapter service in VS 2017 in C#. My test is failing because it is wanting a 400 through 499 response from the HTTPClient. When my test runs the service returns a 500.
So searching I found MockHttpClient nuget package but the examples given are not working when I try them in my test.
example:
https://github.com/codecutout/MockHttpClient/blob/master/README.md
I get an error saying
'MockHttpClient' is a namespace but is used like a type
I also added in a using MockHTTPClient at the top of my test.
What am I doing wrong?
getting error with the below
var mockHttpClient = new MockHttpClient();
mockHttpClient.When("the url I am using").Returns(HttpStatusCode.Forbidden)
It's a name clash with the namespace. The class and namespace share the same name.
Remove the using statement and use this instead:
var mockHttpClient = new MockHttpClient.MockHttpClient();
Poor choice of names for this library and a horrific amount of dependencies. I would stay away if I were you.
UPDATE:
You asked for an alternative so here is what I recently did for a project:
The HttpClient class has a constructor that takes an HttpMessageHandler object, so you can pass your own handler and simulate the behavior.
Create a class that derives from DelegatingHandler and overrides the send behavior:
public class TestHandler : DelegatingHandler
{
private Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handler;
public TestHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handler)
{
_handler = handler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return _handler(request, cancellationToken);
}
public static Task<HttpResponseMessage> OK()
{
return Task.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.OK));
}
public static Task<HttpResponseMessage> BadRequest()
{
return Task.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.BadRequest));
}
}
Then on your test, you use your handler in the constructor:
//Create an instance of the test handler that returns a bad request response
var testHandler = new TestHandler((r, c) =>
{
return TestHandler.BadRequest();
});
//Create the HTTP client
var client = new HttpClient(testHandler);
//Fake call, will never reach out to foo.com
var request = new HttpRequestMessage(HttpMethod.Get, "http://www.foo.com");
request.Content = new StringContent("test");
//This will call the test handler and return a bad request response
var response = client.SendAsync(request).Result;
Notice I have a couple of convenience static methods in there to create the handling functions for me.

Best way to use HTTPClient in ASP.Net Core as a DI Singleton

I am trying to figure out how to best use the HttpClient class in ASP.Net Core.
According to the documentation and several articles, the class is best instantiated once for the lifetime of the application and shared for multiple requests. Unfortunately, I could not find an example of how to correctly do this in Core so I’ve come up with the following solution.
My particular needs require the use of 2 different endpoints (I have an APIServer for business logic and an API driven ImageServer), so my thinking is to have 2 HttpClient singletons that I can use in the application.
I’ve configured my servicepoints in the appsettings.json as follows:
"ServicePoints": {
"APIServer": "http://localhost:5001",
"ImageServer": "http://localhost:5002",
}
Next, I created a HttpClientsFactory that will instantiate my 2 httpclients and hold them in a static Dictionary.
public class HttpClientsFactory : IHttpClientsFactory
{
public static Dictionary<string, HttpClient> HttpClients { get; set; }
private readonly ILogger _logger;
private readonly IOptions<ServerOptions> _serverOptionsAccessor;
public HttpClientsFactory(ILoggerFactory loggerFactory, IOptions<ServerOptions> serverOptionsAccessor) {
_logger = loggerFactory.CreateLogger<HttpClientsFactory>();
_serverOptionsAccessor = serverOptionsAccessor;
HttpClients = new Dictionary<string, HttpClient>();
Initialize();
}
private void Initialize()
{
HttpClient client = new HttpClient();
// ADD imageServer
var imageServer = _serverOptionsAccessor.Value.ImageServer;
client.BaseAddress = new Uri(imageServer);
HttpClients.Add("imageServer", client);
// ADD apiServer
var apiServer = _serverOptionsAccessor.Value.APIServer;
client.BaseAddress = new Uri(apiServer);
HttpClients.Add("apiServer", client);
}
public Dictionary<string, HttpClient> Clients()
{
return HttpClients;
}
public HttpClient Client(string key)
{
return Clients()[key];
}
}
Then, I created the interface that I can use when defining my DI later on. Notice that the HttpClientsFactory class inherits from this interface.
public interface IHttpClientsFactory
{
Dictionary<string, HttpClient> Clients();
HttpClient Client(string key);
}
Now I am ready to inject this into my Dependency container as follows in the Startup class under the ConfigureServices method.
// Add httpClient service
services.AddSingleton<IHttpClientsFactory, HttpClientsFactory>();
All is now set-up to start using this in my controller.
Firstly, I take in the dependency. To do this I created a private class property to hold it, then add it to the constructor signature and finish by assigning the incoming object to the local class property.
private IHttpClientsFactory _httpClientsFactory;
public AppUsersAdminController(IHttpClientsFactory httpClientsFactory)
{
_httpClientsFactory = httpClientsFactory;
}
Finally, we can now use the Factory to request a htppclient and execute a call. Below, an example where I request an image from the imageserver using the httpclientsfactory:
[HttpGet]
public async Task<ActionResult> GetUserPicture(string imgName)
{
// get imageserver uri
var imageServer = _optionsAccessor.Value.ImageServer;
// create path to requested image
var path = imageServer + "/imageuploads/" + imgName;
var client = _httpClientsFactory.Client("imageServer");
byte[] image = await client.GetByteArrayAsync(path);
return base.File(image, "image/jpeg");
}
Done!
I’ve tested this and it work great on my development environment. However, I am not sure if this is the best way to implement this. I remain with the following questions:
Is this solution thread safe? (according to the MS doc: ‘Any public static (Shared in Visual Basic) members of this type are thread safe.’)
Will this set-up be able to handle a heavy load without opening many separate connection?
What to do in ASP.Net core to handle the DNS problem described in ‘Singleton HttpClient? Beware of this serious behaviour and how to fix.’ located at http://byterot.blogspot.be/2016/07/singleton-httpclient-dns.html
Any other improvements or suggestions?
If using .net core 2.1 or higher, the best approach would be to use the new HttpClientFactory. I guess Microsoft realized all the issues people were having so they did the hard work for us. See below for how to set it up.
NOTE: Add a reference to Microsoft.Extensions.Http.
1 - Add a class that uses HttpClient
public interface ISomeApiClient
{
Task<HttpResponseMessage> GetSomethingAsync(string query);
}
public class SomeApiClient : ISomeApiClient
{
private readonly HttpClient _client;
public SomeApiClient (HttpClient client)
{
_client = client;
}
public async Task<SomeModel> GetSomethingAsync(string query)
{
var response = await _client.GetAsync($"?querystring={query}");
if (response.IsSuccessStatusCode)
{
var model = await response.Content.ReadAsJsonAsync<SomeModel>();
return model;
}
// Handle Error
}
}
2 - Register your clients in ConfigureServices(IServiceCollection services) in Startup.cs
var someApiSettings = Configuration.GetSection("SomeApiSettings").Get<SomeApiSettings>(); //Settings stored in app.config (base url, api key to add to header for all requests)
services.AddHttpClient<ISomeApiClient, SomeApiClient>("SomeApi",
client =>
{
client.BaseAddress = new Uri(someApiSettings.BaseAddress);
client.DefaultRequestHeaders.Add("api-key", someApiSettings.ApiKey);
});
3 - Use the client in your code
public class MyController
{
private readonly ISomeApiClient _client;
public MyController(ISomeApiClient client)
{
_client = client;
}
[HttpGet]
public async Task<IActionResult> GetAsync(string query)
{
var response = await _client.GetSomethingAsync(query);
// Do something with response
return Ok();
}
}
You can add as many clients and register as many as needed in your startup with services.AddHttpClient
Thanks to Steve Gordon and his post here for helping me use this in my code!
In answer to a question from #MuqeetKhan regarding using authentication with the httpclient request.
Firstly, my motivation to use DI and a factory was to allow me to extend my application easily to different and multiple API’s and have easy access to that throughout my code. It’s a template I hope to be able to reuse multiple times.
In the case of my ‘GetUserPicture’ controller decribed in the original question above, I indeed for simplicity reasons removed the authentication. Honestly however, I am still in doubt if I need it there to simply retrieve an image from the imageserver. Anyhow, in other controllers I definitely do need it, so…
I’ve implemented Identityserver4 as my authentication server. This provides me with the authentication on top of ASP Identity.
For authorization (using roles in this case), I implemented IClaimsTransformer in my MVC ‘and’ API projects (you can read more about this here at How to put ASP.net Identity Roles into the Identityserver4 Identity token).
Now, the moment I enter my controller I have an authenticated and authorized user for which I can retrieve an access token. I use this token to call my api which is of course calling the same instance of identityserver to verify if the user is authenticated.
The last step is to allow my API to verify if the user is authorized to call the requested api controller. In the request pipeline of the API using IClaimsTransformer as explained before, I retrieve the authorization of the calling user and add it to the incoming claims.
Note that in case of an MVC calling and API, I thus retrieve the authorization 2 times; once in the MVC request pipeline and once in the API request pipeline.
Using this set-up I am able to use my HttpClientsFactory with Authorization and Authentication.
On big security part I am missing is HTTPS of course. I hope I can somehow add it to my factory. I'll update it once I've implemented it.
As always, any suggestions are welcome.
Below an example where I upload an image to the Imageserver using authentication (user must be logged in and have role admin).
My MVC controller calling the ‘UploadUserPicture’:
[Authorize(Roles = "Admin")]
[HttpPost]
public async Task<ActionResult> UploadUserPicture()
{
// collect name image server
var imageServer = _optionsAccessor.Value.ImageServer;
// collect image in Request Form from Slim Image Cropper plugin
var json = _httpContextAccessor.HttpContext.Request.Form["slim[]"];
// Collect access token to be able to call API
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
// prepare api call to update image on imageserver and update database
var client = _httpClientsFactory.Client("imageServer");
client.DefaultRequestHeaders.Accept.Clear();
client.SetBearerToken(accessToken);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("image", json[0])
});
HttpResponseMessage response = await client.PostAsync("api/UserPicture/UploadUserPicture", content);
if (response.StatusCode != HttpStatusCode.OK)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
return StatusCode((int)HttpStatusCode.OK);
}
API handling the user upload
[Authorize(Roles = "Admin")]
[HttpPost]
public ActionResult UploadUserPicture(String image)
{
dynamic jsonDe = JsonConvert.DeserializeObject(image);
if (jsonDe == null)
{
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
// create filname for user picture
string userId = jsonDe.meta.userid;
string userHash = Hashing.GetHashString(userId);
string fileName = "User" + userHash + ".jpg";
// create a new version number
string pictureVersion = DateTime.Now.ToString("yyyyMMddHHmmss");
// get the image bytes and create a memory stream
var imagebase64 = jsonDe.output.image;
var cleanBase64 = Regex.Replace(imagebase64.ToString(), #"^data:image/\w+;base64,", "");
var bytes = Convert.FromBase64String(cleanBase64);
var memoryStream = new MemoryStream(bytes);
// save the image to the folder
var fileSavePath = Path.Combine(_env.WebRootPath + ("/imageuploads"), fileName);
FileStream file = new FileStream(fileSavePath, FileMode.Create, FileAccess.Write);
try
{
memoryStream.WriteTo(file);
}
catch (Exception ex)
{
_logger.LogDebug(LoggingEvents.UPDATE_ITEM, ex, "Could not write file >{fileSavePath}< to server", fileSavePath);
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
memoryStream.Dispose();
file.Dispose();
memoryStream = null;
file = null;
// update database with latest filename and version
bool isUpdatedInDatabase = UpdateDatabaseUserPicture(userId, fileName, pictureVersion).Result;
if (!isUpdatedInDatabase)
{
return new StatusCodeResult((int)HttpStatusCode.NotModified);
}
return new StatusCodeResult((int)HttpStatusCode.OK);
}
For situations when you can't use DI:
using System.Net.Http;
public class SomeClass
{
private static readonly HttpClient Client;
static SomeClass()
{
var handler = new SocketsHttpHandler
{
// Sets how long a connection can be in the pool to be considered reusable (by default - infinite)
PooledConnectionLifetime = TimeSpan.FromMinutes(1),
};
Client = new HttpClient(handler, disposeHandler: false);
}
...
}
Reference https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#alternatives-to-ihttpclientfactory

Moq: How to test a class using Nunit with an internal HttpClient?

I run my tests inside nUnit and normally I can mock out dependencies and have then Return certain values or throw errors.
I have a class that as an internal HttpClient and I would like to test the class, what are my options.
here is my code, its not complete so as not to flood the message. As you can see I am using the HttpClient internally and not injected as a dependency. The class throws a number of custom exceptions, I would like to Moq these otherwise I need to pass REAL username and passwords that would give me the status codes i required to throw the exceptions.
Anyone have an ideas? If I can't mock the httpclient then i can never test my class that it raises exceptions.
Do I really have to change HttpClient to a dependency on the constructor ?
public bool ItemsExist(string itemValue)
{
var relativeUri = string.Format(UrlFormatString, itemValue.ToUpper());
var uri = new Uri(new Uri(this.baseUrl), relativeUri);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", this.encodedCredentials);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetAsync(uri).Result;
switch (response.StatusCode)
{
case HttpStatusCode.Unauthorized:
// DO something here
throw new CustomAuthorizationException();
case HttpStatusCode.Forbidden:
throw new CustomAuthenticationException();
}
return true;
Let me suggest a bit easier solution, without a need to abstract/wrap httpclient, that i believe works perfectly with mocking frameworks.
You need to create a class for fake HttpMessageHandler, like here:
public class FakeHttpMessageHandler : HttpMessageHandler
{
public virtual HttpResponseMessage Send(HttpRequestMessage request)
{
throw new NotImplementedException("Rember to setup this method with your mocking framework");
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return Task.FromResult(Send(request));
}
}
Such created HttpMessageHandler can be used when instantiating HttpClient:
var msgHandler = new Mock<FakeHttpMessageHandler>() { CallBase = true };
var fakeclient = new HttpClient(msgHandler.Object);
And you can setup methods (here using Moq):
msgHandler.Setup(t => t.Send(It.Is<HttpRequestMessage>(
msg =>
msg.Method == HttpMethod.Post &&
msg.RequestUri.ToString() == "http://test.te/item/123")))
.Returns(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound));
You can now user fakeclient instead when necessary.
You can't unit test it like that. It's like you mentioned: HttpClient is a dependency, and as such, it should be injected.
Personally, I would create my own IHttpClient interface, implemented by HttpClientWrapper, which wraps around the System.Net.HttpClient. IHttpClient would then be passed as a dependency to your object's contructor.
As follows, HttpClientWrapper can't be unit tested. I would, however, write a couple of integration tests to make sure the wrapper is well written.
Edit:
IHttpClient doesn't have to be a "valid" interface for HttpClient. It only has to be an interface that suits your needs. It can have as many or as few methods as you want.
Picture this: HttpClient allows you to do many things. But in your project, you're only calling the GetAsync(uri).Result method, nothing else.
Given this scenario, you would write the following interface and implementation:
interface IHttpClient
{
HttpResponseMessage Get(string uri);
}
class HttpClientWrapper : IHttpClient
{
private readonly HttpClient _client;
public HttpClientWrapper(HttpClient client)
{
_client = client;
}
public HttpResponseMessage Get(string uri)
{
return _client.GetAsync(new Uri(uri)).Result;
}
}
So, as I stated previously, the interface only has to suit your needs. You don't have to wrap around the WHOLE HttpClient class.
Obviously, you would then moq your object like this:
var clientMock = new Mock<IHttpClient>();
//setup mock
var myobj = new MyClass(clientMock.object);
And to create an actual object:
var client = new HttpClientWrapper(new HttpClient());
var myobj = new MyClass(client );
Edit2
OH! And don't forget that IHttpClient should also extend the IDisposable interface, very important!
Another option is to use Flurl [disclosure: I'm the author], a library for building and calling URLs. It includes testing helpers that make faking all HTTP incredibly easy. No need for wrapper interfaces.
For starters, your HTTP code itself would look something like this:
using Flurl;
using Flurl.Http;
...
try {
var response = this.baseUrl
.AppendPathSegment(relativeUri)
.WithBasicAuth(username, password)
.WithHeader("Accept", "application/json")
.GetAsync().Result;
return true;
}
catch (FlurlHttpException ex) {
// Flurl throws on unsuccessful responses. Null-check ex.Response,
// then do your switch on ex.Response.StatusCode.
}
Now for the testing fun:
using Flurl.Http.Testing;
...
[Test]
public void ItemsExists_SuccessResponse() {
// kick Flurl into test mode - all HTTP calls will be faked and recorded
using (var httpTest = new HttpTest()) {
// arrange
test.RespondWith(200, "{status:'ok'}");
// act
sut.ItemExists("blah");
// assert
test.ShouldHaveCalled("http://your-url/*");
}
}
Get it on NuGet:
PM> Install-Package Flurl.Http

Categories

Resources