You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用TestServer测试路由时遇localhost:80连接被主动拒绝问题求助

解决TestServer测试时“目标机器主动拒绝连接”的问题

问题核心原因

你遇到的连接拒绝错误,本质是替换了WebApplicationFactory创建的HttpClient实例:CreateClient()返回的HttpClient是绑定到TestServer内存管道的,不会发起真实网络请求;但你重新new了一个带HttpClientHandler的HttpClient,这个新客户端会尝试访问真实的localhost:80端口,而TestServer并未监听该端口,因此导致连接被拒绝。

另外,你同时混用Cookie和Bearer Token可能引发认证逻辑冲突,且手动注册AuthenticationService的方式可能覆盖框架默认服务,进而导致404/401等异常。

修复步骤

1. 保留TestServer绑定的HttpClient,禁止重新实例化

修改测试方法中的HttpClient处理逻辑,直接给原有客户端添加Cookie,不要创建新的HttpClient:

[TestMethod]
[DoNotParallelize]
public async Task AppController_ShouldRouteCorrectly()
{
    // Arrange
    var _client = CreateClient();

    // 给现有客户端添加Cookie(无需重新实例化HttpClient)
    if (_client.BaseAddress != null)
    {
        var cookieContainer = new CookieContainer();
        cookieContainer.Add(_client.BaseAddress, new Cookie("Cookie", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwMDE5IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvZW1haWxhZGRyZXNzIjoiaW1leWVyc0B0aGluaXguY29tIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImltZXllcnNAdGhpbml4LmNvbSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2dpdmVubmFtZSI6IklzYWFjIE1leWVycyIsImlzbG9ja2Vkb3V0IjoiRmFsc2UiLCJpc2FwcHJvdmVkIjoiVHJ1ZSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6WyIqIiwibWVtYmVyIiwiZGV2ZWxvcGVyIiwic3VwcG9ydCJdLCJuYmYiOjE3MTE0ODg1NDAsImV4cCI6MTcxNDA4MDU0MCwiaXNzIjoiaHR0cDovL2lzdGF0dXMuY29tIiwiYXVkIjoiaHR0cDovL2lzdGF0dXMuY29tIn0.MalKNawXYB0fdfe1644Ut_4rjb1W2QaBw236WVJrHTM"));
        
        // 包装原有Handler以支持Cookie
        var originalHandler = _client.GetHttpMessageHandler();
        _client.Dispose();
        _client = new HttpClient(new HttpClientHandler { CookieContainer = cookieContainer }) 
        { 
            BaseAddress = new Uri("http://localhost") 
        };
    }

    // 添加Bearer Token(二选一即可,避免认证冲突)
    _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEwMDE5IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvZW1haWxhZGRyZXNzIjoiaW1leWVyc0B0aGluaXguY29tIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImltZXllcnNAdGhpbml4LmNvbSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2dpdmVubmFtZSI6IklzYWFjIE1leWVycyIsImlzbG9ja2Vkb3V0IjoiRmFsc2UiLCJpc2FwcHJvdmVkIjoiVHJ1ZSIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6WyIqIiwibWVtYmVyIiwiZGV2ZWxvcGVyIiwic3VwcG9ydCJdLCJuYmYiOjE3MTE0ODg1NDAsImV4cCI6MTcxNDA4MDU0MCwiaXNzIjoiaHR0cDovL2lzdGF0dXMuY29tIiwiYXVkIjoiaHR0cDovL2lzdGF0dXMuY29tIn0.MalKNawXYB0fdfe1644Ut_4rjb1W2QaBw236WVJrHTM");

    // Act
    var devResponse = await _client.GetAsync("/developer");
    var response = await _client.GetAsync("/api/subscription-bundles");

    // Assert
    Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}

2. 简化认证Mock,避免与框架默认服务冲突

手动注册AuthenticationService可能覆盖框架默认逻辑,建议先移除原有服务再注册:

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
    builder.ConfigureTestServices(services =>
    {
        // 移除原有IAuthenticationService,避免冲突
        var authServiceDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IAuthenticationService));
        if (authServiceDescriptor != null)
        {
            services.Remove(authServiceDescriptor);
        }

        // 注册Mock服务
        services.AddSingleton<IAuthenticationSchemeProvider, MockAuthenticationSchemeProvider>();
        services.AddSingleton<IAuthenticationHandlerProvider, MockAuthenticationHandlerProvider>();
        services.AddSingleton<IClaimsTransformation, MockClaimsTransformation>();
        services.AddSingleton<IOptions<AuthenticationOptions>, MockOptionsAuthentication>();
        services.AddSingleton<MockAuthenticationService>();

        // 注册自定义AuthenticationService
        services.AddSingleton<IAuthenticationService>(sp =>
        {
            var mockSchemeProvider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
            var mockHandlers = sp.GetRequiredService<IAuthenticationHandlerProvider>();
            var mockTransform = sp.GetRequiredService<IClaimsTransformation>();
            var mockOptions = sp.GetRequiredService<IOptions<AuthenticationOptions>>();

            return new AuthenticationService(mockSchemeProvider, mockHandlers, mockTransform, mockOptions);
        });

        // 保留JWT配置
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(options =>
        {
            options.RequireHttpsMetadata = false;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = false,
                ValidateIssuerSigningKey = false,
            };
        });
    });
}

3. 可选:先跳过认证验证路由

如果暂时不需要测试认证逻辑,可以添加全局允许匿名规则,先验证路由是否可达:

builder.ConfigureTestServices(services =>
{
    services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .AllowAnonymous()
            .Build();
    });
});

关键注意事项

  • 永远不要替换WebApplicationFactory创建的HttpClient实例,否则会脱离TestServer的内存管道,发起真实网络请求。
  • 认证Mock需避免与框架默认服务冲突,优先使用框架扩展方法配置。
  • 测试时尽量单一变量,先验证路由可达性,再逐步添加认证逻辑测试。

内容的提问来源于stack exchange,提问作者IMeyers20

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 22:44:53