2

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.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Kal
  • 141
  • 1
  • 12
  • 1
    Provide a [mcve]. What you have here currently is not complete. The same code does match match what is used in the test. This makes the question a bit confusing. – Nkosi Mar 20 '18 at 10:20
  • Singletons and static properties in general don't play well with mocking and stubbing, particularly with Moq. This question https://stackoverflow.com/questions/12580015/ gives a good discussion of the reasons and some possible workrounds. – mountain traveller Mar 21 '18 at 13:47

0 Answers0