0

我有一个这样的静态 HttpClient 设置:

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);
    }
}

称之为——Client.Instance.ExecuteRequest("request string here");

有一个构造函数可以采用模拟的 Polly 策略和模拟的 HttpClient。

Policies类看起来像这样:

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);
    }
}

在实际情况中,我只使用PolicyWrap.

策略解释 - 每 1 秒超时重试被触发。FallbackPolicy被调用后可以重试 3 次。在单元测试中,我想模拟一个 HttpClient ,因此每次调用它都会超时(> 1 秒睡眠),因此将触发 3 次超时/重试并FallbackPolicy启动。

这里的问题是 HttpClient 是静态的,我希望它保持这种状态,所以我不能使用依赖注入来模拟它。

到目前为止,我的测试如下所示:

    [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);
    }

感谢您的任何意见。

编辑: 我已经设法使用依赖注入从单元测试中模拟 HttpClient,方法是使其非静态并将其传递给var httpMessageHandler = new Mock<HttpMessageHandler>();构造函数。(我不想做的方式,但没有想出另一种方式)

重试策略现在似乎可以正确触发,但是 Timeout 没有按预期工作,将我的问题转移到:Polly TimeoutPolicy delegate not being hit in async context

4

0 回答 0