使用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
相关产品推荐
相关产品推荐

