I have a static HttpClient set up like this:
public class Client
{
private static readonly HttpClient _httpClient = new HttpClient()
; //WHY? BECAUSE - https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
private static Client instance;
private static readonly object padlock = new object();
private Policies policies = new Policies();
public Client(HttpClient httpClient)
{
_httpClient = httpClient;
}
public static Client Instance
{
get
{
lock (padlock)
{
if (instance != null) return instance;
instance = new Client();
_httpClient.BaseAddress = new Uri("http://whatever.com/v1/");
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));
return instance;
}
}
}
public async Task<string> ExecuteRequest(string request)
{
var response = await policies.PolicyWrap.ExecuteAsync(async token => await _httpClient.PostAsync("", new StringContent(xml)).ConfigureAwait(false), CancellationToken.None);
if (!response.IsSuccessStatusCode)
throw new Exception("exception");
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
}
To call it - Client.Instance.ExecuteRequest("request string here");
There is a constructor which can take a mocked Polly's policy and a mocked HttpClient.
Policies
class looks like this:
public class Policies
{
public TimeoutPolicy<HttpResponseMessage> TimeoutPolicy
{
get
{
return Policy.TimeoutAsync<HttpResponseMessage>(1, onTimeoutAsync: TimeoutDelegate);
}
}
public RetryPolicy<HttpResponseMessage> RetryPolicy
{
get
{
return Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.Or<TimeoutRejectedException>()
.RetryAsync(3, onRetry: RetryDelegate);
}
}
public FallbackPolicy<HttpResponseMessage> FallbackPolicy
{
get
{
return Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.Or<TimeoutRejectedException>()
.FallbackAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError), onFallbackAsync: FallbackDelegate);
}
}
public PolicyWrap<HttpResponseMessage> PolicyWrap
{
get
{
return Policy.WrapAsync(FallbackPolicy, RetryPolicy, TimeoutPolicy);
}
}
private Task TimeoutDelegate(Context context, TimeSpan timeSpan, Task task)
{
//LOG HERE
return Task.FromResult<object>(null);
}
private void RetryDelegate(DelegateResult<HttpResponseMessage> delegateResult, int i)
{
//LOG HERE
}
private Task FallbackDelegate(DelegateResult<HttpResponseMessage> delegateResult, Context context)
{
//LOG HERE
return Task.FromResult<object>(null);
}
}
In a real situation I am only using PolicyWrap
.
Policies explanation - after each 1 second timeout retry gets fired. There can be 3 retries after FallbackPolicy
gets called.
In a unit test, I would like to mock an HttpClient so it would timeout (>1 sec Sleep) each time it gets called so 3 timeouts/retries would be fired and a FallbackPolicy
would initiate.
The problem here is that the HttpClient is static and I would like it to remain that way so I can not mock it using dependency injection.
So far my test looks like this:
[TestMethod]
public async Task HttpClientTest()
{
//Arrange
var fakeResponse = "blabla";
var httpMessageHandler = new Mock<HttpMessageHandler>();
httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.Returns(Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.InternalServerError,//This is where it should fail each time called
Content = new StringContent(fakeResponse, Encoding.UTF8, "text/xml")
}));
var httpClient = new HttpClient(httpMessageHandler.Object);
httpClient.BaseAddress = new Uri(@"http://some.address.com/v1/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/xml"));
IAsyncPolicy<HttpResponseMessage> mockPolicy = Policy.NoOpAsync<HttpResponseMessage>();
var client = new Client(mockPolicy, httpClient);
//Act
var result = await client.ExecuteRequest("test request"));
//Assert
Assert.AreEqual("blabla", result.ResponseMessage);
}
Thank you for any input.
EDIT:
I have managed to mock the HttpClient from a unit test using dependency injection by just making it non-static and passing the var httpMessageHandler = new Mock<HttpMessageHandler>();
into the constructor. (The way I did not want to do, but did not figure out another way)
Retry policy seems to be firing correctly now, however Timeout does not work as expected, transferring my question to: Polly TimeoutPolicy delegate not being hit in an async context.