I'd like to unit test the following class.
How can I mock the HttpClient when it's used as a static readonly field in a static class?
This question doesn't help as the OP is using an instance field for the HttpClient.
Here's my class:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Integration.IdentityProvider
{
public static class IdentityProviderApiCaller
{
private static readonly HttpClient HttpClient;
static IdentityProviderApiCaller()
{
HttpClient = HttpClientFactory.Create();
HttpClient.BaseAddress = new Uri("https://someApi.com");
HttpClient.DefaultRequestHeaders.Add("Accept", "application/json");
}
public static async Task<IList<AddGroupResult>> AddGroups(AddGroup[] addGroupsModel)
{
var content = GetContent(addGroupsModel);
var urlPath = "/groups";
var result = await HttpClient.PutAsync(urlPath, content).ConfigureAwait(false);
return await GetObject<IList<AddGroupResult>>(result).ConfigureAwait(false);
}
private static async Task<T> GetObject<T>(HttpResponseMessage result) where T : class
{
if (result.IsSuccessStatusCode)
{
return await DeserializeObject<T>(result.Content).ConfigureAwait(false);
}
var errorResult = await DeserializeObject<ErrorResult>(result.Content).ConfigureAwait(false);
throw new Exception(errorResult.ExceptionMessage);
}
private static async Task<T> DeserializeObject<T>(HttpContent content) where T : class
{
var jsonContent = await content.ReadAsStringAsync().ConfigureAwait(false);
T obj;
try
{
obj = JsonConvert.DeserializeObject<T>(jsonContent);
}
catch (JsonSerializationException)
{
return await Task.FromResult<T>(null).ConfigureAwait(false);
}
return obj;
}
private static StringContent GetContent<T>(T obj)
{
var payload = JsonConvert.SerializeObject(obj);
var content = new StringContent(payload, Encoding.UTF8, "application/json");
return content;
}
}
public class AddGroup
{
public string Name { get; set; }
public string Description { get; set; }
}
public class AddGroupResult
{
public bool IsSuccessful { get; set; }
}
public class ErrorResult
{
public string ExceptionMessage { get; set; }
}
}
In order to test things, I will suggest a few changes.
Remove all
statickeyword from your code. I understand you would like to have one thing that does the job, but this can be achieved by having only one instance of the class. This also means it is easier to test your class - create a new instance for a test. You could restrict how many instances is created during the configuration/setup stage in aspnet core (if you are using that).Change the constructor from static to instance and pass the client as a dependency to it. The new ctor will look something like this:
The key point here, is that you provide the dependency of
IdentityProviderApiCallerin a ctor, so you can unit test it. In a unit test you provide amockfor the HTTP client, so you can set expectation forgetorpostand see if the method is being called correctly. In anintegrationtest you can pass a real instance of HTTP client, so you can actually hit your back end services.AddGroupclass to aGroup, then the code gets easier to read. Imagine you can also have APIs toDelete(Group group)or list groups. Having a nameAddGroupfor the group will be confusing. Also, you can simplify the theasync. So all together the code should look something like:Exceptionis very broad. Consider common exceptionsArgumentException,InvalidOperationException, etc. You could create your own exception too, but maybe best check what built-in exceptions are availableThere may be a class, specifically for aspnet core, where you can return a failure code, it may look something like:
The idea is that you can specify which error code is returned to the client of your API, such as 401, 400. If an exception is thrown, I think it will be status code 500 Internal Server error, which may not be ideal for the client of the API
Now, back to the unit testing, in meta-code, this is how it will look: