gpt4 book ai didi

c# - 在 IHttpClientFactory 注入(inject)时模拟 HttpClient 处理程序

转载 作者:行者123 更新时间:2023-12-05 05:00:37 24 4
gpt4 key购买 nike

我创建了一个自定义库,它会自动为依赖于 HttpClient 的特定服务设置 Polly 策略。

这是使用 IServiceCollection 扩展方法和类型化客户端方法完成的。一个简化的例子:

public static IHttpClientBuilder SetUpFooServiceHttpClient(this IServiceCollection services)
{
return services
.AddHttpClient<FooService>()
.AddPolicyHandler(GetRetryPolicy());
}

public class FooService
{
private readonly HttpClient _client;

public FooService(HttpClient httpClient)
{
_client = httpClient;
}

public void DoJob()
{
var test = _client.GetAsync("http://example.com");
}
}

请注意,我的真实代码使用了泛型类型和选项构建器,但为了简单起见,我省略了那部分。我测试的目的是确认我的选项构建器正确应用了我希望它应用的策略。为了这里的示例,我们假设它是我要测试的硬编码重试策略。

我现在想测试这个库是否正确地将 Polly 策略注册到我注入(inject)的 HttpClient 依赖项中。

Note
There are many answers to be found online and on StackOverflow where the suggestion is to construct the HttpClient yourself, i.e.: new HttpClient(new MyMockedHandler());, but this defeats my purpose of needing to test if the actual IHttpClientFactory is constructing httpclients with the requested policies.

为此,我想用 real HttpClient 进行测试,它由 real IHttpClientFactory 生成,但我希望它的处理程序被模拟,这样我就可以避免发出实际的网络请求并人为地导致错误的响应。

我正在使用 AddHttpMessageHandler() 注入(inject)模拟处理程序,但工厂似乎忽略了这一点。

这是我的测试夹具:

public class BrokenDelegatingHandler : DelegatingHandler
{
public int SendAsyncCount = 0;

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
SendAsyncCount++;

return Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError));
}
}

private BrokenDelegatingHandler _brokenHandler = new BrokenDelegatingHandler();

private FooService GetService()
{
var services = new ServiceCollection();

services.AddTransient<BrokenDelegatingHandler>();

var httpClientBuilder = services.SetUpFooServiceHttpClient();

httpClientBuilder.AddHttpMessageHandler(() => _brokenHandler);

services.AddSingleton<FooService>();

return services
.BuildServiceProvider()
.GetRequiredService<FooService>();
}

这是我的测试:

[Fact]
public void Retries_client_connection()
{
int retryCount = 3;

var service = GetService();

_brokenHandler.SendAsyncCount.Should().Be(0); // PASS

var result = service.DoJob();

_brokenHandler.SendAsyncCount.Should().Be(retryCount); // FAIL: expected 3 but got 0
}

当我调试测试时,处理程序的断点从未命中,响应返回为 200(因为它实际上连接到 URL,而不是命中模拟处理程序)。

为什么我的模拟处理程序被 http 客户端工厂忽略了?

请注意,我也会接受任何允许我以另一种有效方式测试政策的答案。

我知道我可以只使用损坏的 URL 字符串,但我需要在我的测试中测试特定的 http 响应。

最佳答案

几个月前我们遇到过类似的问题。如何测试注入(inject) HttpClient装饰有正确的政策。 (我们使用了 Retry > CircuitBreaker > Timeout 策略链)。

我们最终创建了多个集成测试。我们用过WireMock.NET创建服务器 stub 。所以,这一切的重点是让 ASP.NET DI 发挥它的魔力,然后仔细检查 stub 的日志。

我们创建了两个包装 WireMock 设置的基类(我们有一个 POST 端点)。

完美服务器

internal abstract class FlawlessServiceMockBase
{
protected readonly WireMockServer server;
private readonly string route;

protected FlawlessServiceMockBase(WireMockServer server, string route)
{
this.server = server;
this.route = route;
}

public virtual void SetupMockForSuccessResponse(IResponseBuilder expectedResponse = null,
HttpStatusCode expectedStatusCode = HttpStatusCode.OK)
{
server.Reset();

var endpointSetup = Request.Create().WithPath(route).UsingPost();
var responseSetup = expectedResponse ?? Response.Create().WithStatusCode(expectedStatusCode);

server.Given(endpointSetup).RespondWith(responseSetup);
}
}

故障服务器

(我们使用了scenarios来模拟超时)

internal abstract class FaultyServiceMockBase
{
protected readonly WireMockServer server;
protected readonly IRequestBuilder endpointSetup;
protected readonly string scenario;

protected FaultyServiceMockBase(WireMockServer server, string route)
{
this.server = server;
this.endpointSetup = Request.Create().WithPath(route).UsingPost();
this.scenario = $"polly-setup-test_{this.GetType().Name}";
}

public virtual void SetupMockForFailedResponse(IResponseBuilder expectedResponse = null,
HttpStatusCode expectedStatusCode = HttpStatusCode.InternalServerError)
{
server.Reset();

var responseSetup = expectedResponse ?? Response.Create().WithStatusCode(expectedStatusCode);

server.Given(endpointSetup).RespondWith(responseSetup);
}

public virtual void SetupMockForSlowResponse(ResilienceSettings settings, string expectedResponse = null)
{
server.Reset();

int higherDelayThanTimeout = settings.HttpRequestTimeoutInMilliseconds + 500;

server
.Given(endpointSetup)
.InScenario(scenario)
//NOTE: There is no WhenStateIs
.WillSetStateTo(1)
.WithTitle(Common.Constants.Stages.Begin)
.RespondWith(DelayResponse(higherDelayThanTimeout, expectedResponse));

for (var i = 1; i < settings.HttpRequestRetryCount; i++)
{
server
.Given(endpointSetup)
.InScenario(scenario)
.WhenStateIs(i)
.WillSetStateTo(i + 1)
.WithTitle($"{Common.Constants.Stages.RetryAttempt} #{i}")
.RespondWith(DelayResponse(higherDelayThanTimeout, expectedResponse));
}

server
.Given(endpointSetup)
.InScenario(scenario)
.WhenStateIs(settings.HttpRequestRetryCount)
//NOTE: There is no WillSetStateTo
.WithTitle(Common.Constants.Stages.End)
.RespondWith(DelayResponse(1, expectedResponse));
}

private static IResponseBuilder DelayResponse(int delay) => Response.Create()
.WithDelay(delay)
.WithStatusCode(200);

private static IResponseBuilder DelayResponse(int delay, string response) =>
response == null
? DelayResponse(delay)
: DelayResponse(delay).WithBody(response);
}

慢速处理的简单测试

(proxyApiInitializerWebApplicationFactory<Startup> 派生类的实例)

[Fact]
public async Task GivenAValidInout_AndAServiceWithSlowProcessing_WhenICallXYZ_ThenItCallsTheServiceSeveralTimes_AndFinallySucceed()
{
//Arrange - Proxy request
HttpClient proxyApiClient = proxyApiInitializer.CreateClient();
var input = new ValidInput();

//Arrange - Service
var xyzSvc = new FaultyXYZServiceMock(xyzServer.Value);
xyzSvc.SetupMockForSlowResponse(resilienceSettings);

//Act
var actualResult = await CallXYZAsync(proxyApiClient, input);

//Assert - Response
const HttpStatusCode expectedStatusCode = HttpStatusCode.OK;
actualResult.StatusCode.ShouldBe(expectedStatusCode);

//Assert - Resilience Policy
var logsEntries = xyzServer.Value.FindLogEntries(
Request.Create().WithPath(Common.Constants.Routes.XYZService).UsingPost());
logsEntries.Last().MappingTitle.ShouldBe(Common.Constants.Stages.End);
}

XYZ 服务器初始化:

private static Lazy<WireMockServer> xyzServer;

public ctor()
{
xyzServer = xyzServer ?? InitMockServer(API.Constants.EndpointConstants.XYZServiceApi);
}

private Lazy<WireMockServer> InitMockServer(string lookupKey)
{
string baseUrl = proxyApiInitializer.Configuration.GetValue<string>(lookupKey);
return new Lazy<WireMockServer>(
WireMockServer.Start(new FluentMockServerSettings { Urls = new[] { baseUrl } }));
}

希望对您有所帮助。

关于c# - 在 IHttpClientFactory 注入(inject)时模拟 HttpClient 处理程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63032013/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com