我有一个这样的静态 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。