为什么 .NetCore HttpClient 在我的单元测试中的第二次重试中被处置?
Why is .NetCore HttpClient disposed in second retry in my Unit Test?
我想在我的 UT 中测试我的 httpclient post 重试功能,这里我模拟了 HttpFactory:
both the first and second time, the HttpFactory always returns HttpStatusCode.InternalServerError
public class MyServiceClient
{
private readonly IHttpClientFactory _clientFactory;
public MyServiceClient(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetResponse(string test= "te")
{
using var client = _clientFactory.CreateClient("MyClient");
var content = new StringContent("{}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://www.contoso.com/",content);
if (!response.IsSuccessStatusCode)
{
throw new ApplicationException("Application Error!");
}
var result = await response.Content.ReadAsStringAsync();
return result;
}
public async Task<string> PollyExecute()
{
try
{
var policy = Policy
.Handle<Exception>()
.WaitAndRetryAsync(3,
count => TimeSpan.FromSeconds(2),
(ex, timeSpan,retrycount, context) =>
{
Console.WriteLine(ex);
});
var response = await policy.ExecuteAsync(()=>GetResponse());
return response;
}
catch (Exception e)
{
Console.WriteLine(e);
throw ;
}
}
}
然后我将我的策略用于 运行 客户端 post 异步方法,在我第一次重试时没有问题,我得到了例外的 500 内部服务器错误。
public class HttpClientTest
{
[Fact]
public async Task PoliceTest()
{
var messageHandler = new StubHttpMessageHandler(HttpStatusCode.InternalServerError, "Error!!!!");
var httpClient = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var factory = Substitute.For<IHttpClientFactory>();
factory.CreateClient(Arg.Any<string>()).Returns(httpClient, httpClient);
var client = new MyServiceClient(factory);
var result = await client.PollyExecute();
}
}
public sealed class StubHttpMessageHandler : HttpMessageHandler
{
public string _response;
public HttpStatusCode _statusCode;
public StubHttpMessageHandler(HttpStatusCode statusCode, string response)
{
_statusCode = statusCode;
_response = response;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return Task.FromResult(Send(request));
}
private HttpResponseMessage Send(HttpRequestMessage request)
{
return new HttpResponseMessage
{
Content = new StringContent(_response),
StatusCode = _statusCode,
};
}
}
但是,在第二次重试中,当运行ning post异步方法时,
它抛出一个异常,表示 httpclient 已被处理。
为什么?有朋友知道原因吗?提前致谢!
我添加了更多示例代码,并得到了初步调查:
在 Polly 的第二次,如果我删除 Using in
using var client = _clientFactory.CreateClient("MyClient");
那么也不例外。我猜应该是client的作用域导致的,但是实际上在执行PostAsync()的时候,Client还有一些值,好像没有disposed。
很奇怪。
我认为您只是在测试设置中错误地模拟了 IHttpClientFactory
。按照设计默认实现工厂 return 的新客户端 (Microsoft docs):
Each call to CreateClient(String) is guaranteed to return a new HttpClient instance. Callers may cache the returned HttpClient instance indefinitely or surround its use in a using block to dispose it when desired.
在示例中,您提供了与工厂 return 相同的 httpClient
,该工厂在首次使用后被处置。相同的处置对象一次又一次地被模拟工厂 return 编辑,导致处置错误。
只需将您的示例修改为 return 个客户端的不同实例(请原谅重复的代码)避免处理错误:
...
var httpClient = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient1 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient2 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient3 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var factory = Substitute.For<IHttpClientFactory>();
factory
.CreateClient(Arg.Any<string>())
.Returns(httpClient, httpClient1, httpClient2, httpClient3);
...
您的模拟 return 每次都是相同的 httpClient
对象。 HttpClient
一旦丢弃就不能重复使用。
每次设置模拟到return一个新实例:
var factory = Substitute.For<IHttpClientFactory>();
factory.CreateClient(Arg.Any<string>()).Returns( _ => new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
});
首先,您需要每次都在 mock 中设置一个新的 HttpClient 实例。
var factory = Substitute.For<IHttpClientFactory>();
factory.CreateClient(Arg.Any<string>())
.Returns(new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
}, new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
});
其次,如果你不想在没有using
子句的情况下获取任何值,你需要在使用后手动调用Dispose
方法。
var client = _clientFactory.CreateClient("MyClient");
try
{
var content = new StringContent("{}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://www.contoso.com/", content);
if (!response.IsSuccessStatusCode)
//... your whole logic
}
finally
{
if (client != null) { client.Dispose(); }
}
我想在我的 UT 中测试我的 httpclient post 重试功能,这里我模拟了 HttpFactory:
both the first and second time, the HttpFactory always returns HttpStatusCode.InternalServerError
public class MyServiceClient
{
private readonly IHttpClientFactory _clientFactory;
public MyServiceClient(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetResponse(string test= "te")
{
using var client = _clientFactory.CreateClient("MyClient");
var content = new StringContent("{}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://www.contoso.com/",content);
if (!response.IsSuccessStatusCode)
{
throw new ApplicationException("Application Error!");
}
var result = await response.Content.ReadAsStringAsync();
return result;
}
public async Task<string> PollyExecute()
{
try
{
var policy = Policy
.Handle<Exception>()
.WaitAndRetryAsync(3,
count => TimeSpan.FromSeconds(2),
(ex, timeSpan,retrycount, context) =>
{
Console.WriteLine(ex);
});
var response = await policy.ExecuteAsync(()=>GetResponse());
return response;
}
catch (Exception e)
{
Console.WriteLine(e);
throw ;
}
}
}
然后我将我的策略用于 运行 客户端 post 异步方法,在我第一次重试时没有问题,我得到了例外的 500 内部服务器错误。
public class HttpClientTest
{
[Fact]
public async Task PoliceTest()
{
var messageHandler = new StubHttpMessageHandler(HttpStatusCode.InternalServerError, "Error!!!!");
var httpClient = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var factory = Substitute.For<IHttpClientFactory>();
factory.CreateClient(Arg.Any<string>()).Returns(httpClient, httpClient);
var client = new MyServiceClient(factory);
var result = await client.PollyExecute();
}
}
public sealed class StubHttpMessageHandler : HttpMessageHandler
{
public string _response;
public HttpStatusCode _statusCode;
public StubHttpMessageHandler(HttpStatusCode statusCode, string response)
{
_statusCode = statusCode;
_response = response;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return Task.FromResult(Send(request));
}
private HttpResponseMessage Send(HttpRequestMessage request)
{
return new HttpResponseMessage
{
Content = new StringContent(_response),
StatusCode = _statusCode,
};
}
}
但是,在第二次重试中,当运行ning post异步方法时,
它抛出一个异常,表示 httpclient 已被处理。
为什么?有朋友知道原因吗?提前致谢!
我添加了更多示例代码,并得到了初步调查:
在 Polly 的第二次,如果我删除 Using in
using var client = _clientFactory.CreateClient("MyClient");
那么也不例外。我猜应该是client的作用域导致的,但是实际上在执行PostAsync()的时候,Client还有一些值,好像没有disposed。
很奇怪。
我认为您只是在测试设置中错误地模拟了 IHttpClientFactory
。按照设计默认实现工厂 return 的新客户端 (Microsoft docs):
Each call to CreateClient(String) is guaranteed to return a new HttpClient instance. Callers may cache the returned HttpClient instance indefinitely or surround its use in a using block to dispose it when desired.
在示例中,您提供了与工厂 return 相同的 httpClient
,该工厂在首次使用后被处置。相同的处置对象一次又一次地被模拟工厂 return 编辑,导致处置错误。
只需将您的示例修改为 return 个客户端的不同实例(请原谅重复的代码)避免处理错误:
...
var httpClient = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient1 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient2 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var httpClient3 = new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
};
var factory = Substitute.For<IHttpClientFactory>();
factory
.CreateClient(Arg.Any<string>())
.Returns(httpClient, httpClient1, httpClient2, httpClient3);
...
您的模拟 return 每次都是相同的 httpClient
对象。 HttpClient
一旦丢弃就不能重复使用。
每次设置模拟到return一个新实例:
var factory = Substitute.For<IHttpClientFactory>();
factory.CreateClient(Arg.Any<string>()).Returns( _ => new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
});
首先,您需要每次都在 mock 中设置一个新的 HttpClient 实例。
var factory = Substitute.For<IHttpClientFactory>();
factory.CreateClient(Arg.Any<string>())
.Returns(new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
}, new HttpClient(messageHandler)
{
BaseAddress = new Uri("http://mockuri")
});
其次,如果你不想在没有using
子句的情况下获取任何值,你需要在使用后手动调用Dispose
方法。
var client = _clientFactory.CreateClient("MyClient");
try
{
var content = new StringContent("{}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("http://www.contoso.com/", content);
if (!response.IsSuccessStatusCode)
//... your whole logic
}
finally
{
if (client != null) { client.Dispose(); }
}