.NET Core集成测试问题:App1调用App2失败排查
集成测试中App1调用App2失败的问题解决
规格说明
- 两个.NET Core应用:App1和App2
- App1 运行URI:
http://localhost:1111 - App2 运行URI:
http://localhost:2222
场景
向App1发送GET请求,App1会内部调用App2的GET接口,并将App2的响应拼接后返回给调用方。
问题
本地正常运行时,调用http://localhost:1111/first/getstringfromapp2能成功触发App1调用App2;但执行集成测试时,直接调用App2的测试用例正常通过,通过App1调用App2的用例却报连接被拒绝异常。尝试手动指定服务URI或使用无端口地址均无法解决。
现有代码
App1 控制器代码
using Microsoft.AspNetCore.Mvc; namespace App1.Controllers { [ApiController] [Route("[controller]")] public class FirstController : ControllerBase { [HttpGet("getstringfromapp2")] public async Task<string> GetStringFromApp2() { var appname = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; using var client = new HttpClient(); HttpResponseMessage htpResponse; string response = ""; try { htpResponse = await client.GetAsync("http://localhost:2222/second/appname"); if (!htpResponse.IsSuccessStatusCode) { Console.WriteLine($"Failed to call App2: {htpResponse.StatusCode}"); } response = await htpResponse.Content.ReadAsStringAsync(); } catch (HttpRequestException e) { Console.WriteLine($"HttpRequestException: {e.Message}"); } return appname + " --> " + response; } } }
App2 控制器代码
using Microsoft.AspNetCore.Mvc; namespace App1.Controllers // 注意:此处命名空间应为App2.Controllers,属于笔误 { [ApiController] [Route("[controller]")] public class SecondController : ControllerBase { [HttpGet("appname")] public async Task<string> GetAppName() { var appname = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; return appname; } } }
App1 Program.cs
namespace App1 { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); } } }
App2 Program.cs
namespace App2 { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); } } }
测试类代码
using Microsoft.AspNetCore.Mvc.Testing; using System.Net; namespace Tests.NUnit.Integration { public class App1_App2_Tests { private WebApplicationFactory<App1.Program> _app1Factory; private WebApplicationFactory<App2.Program> _app2Factory; private HttpClient _app1Client; private HttpClient _app2Client; [SetUp] public void SetUp() { _app2Factory = new WebApplicationFactory<App2.Program>(); _app2Client = _app2Factory.CreateClient(); _app2Factory.Server.BaseAddress = new Uri("http://localhost:2222"); _app2Client.BaseAddress = _app2Factory.Server.BaseAddress; _app1Factory = new WebApplicationFactory<App1.Program>(); _app1Client = _app1Factory.CreateClient(); _app1Factory.Server.BaseAddress = new Uri("http://localhost:1111"); _app1Client.BaseAddress = _app1Factory.Server.BaseAddress; } [Test] public async Task App1_callsApp2_ReturnNamesOfBothAppsConcatenated() { var response = await _app1Client.GetAsync("/first/getstringfromapp2"); var content = await response.Content.ReadAsStringAsync(); Assert.That(HttpStatusCode.OK, Is.EqualTo(response.StatusCode)); Assert.That(content, Contains.Substring("App1 --> App2")); } [Test] public async Task App2_IsCalled_ReturnsAppName() { var response = await _app2Client.GetAsync("/second/appname"); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine($"App2 Response: {content}"); Assert.That(content, Is.Not.Empty); } [TearDown] public void TearDown() { _app1Factory.Dispose(); _app2Factory.Dispose(); _app1Client.Dispose(); _app2Client.Dispose(); } } }
问题原因与解决方案
核心原因
WebApplicationFactory默认使用随机端口启动测试服务器,即便手动设置Server.BaseAddress,实际测试服务器并未监听你指定的localhost:2222端口。App1代码中硬编码了App2的地址,导致测试时请求无法到达实际运行的App2测试服务器。
解决步骤
1. 让App1通过配置读取App2地址
移除硬编码的App2地址,改为从配置文件读取,方便测试时动态替换:
修改App1控制器,注入IConfiguration并读取配置:
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; namespace App1.Controllers { [ApiController] [Route("[controller]")] public class FirstController : ControllerBase { private readonly IConfiguration _configuration; public FirstController(IConfiguration configuration) { _configuration = configuration; } [HttpGet("getstringfromapp2")] public async Task<string> GetStringFromApp2() { var appname = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; var app2BaseUrl = _configuration["App2BaseUrl"] ?? "http://localhost:2222"; using var client = new HttpClient(); HttpResponseMessage htpResponse; string response = ""; try { htpResponse = await client.GetAsync($"{app2BaseUrl}/second/appname"); if (!htpResponse.IsSuccessStatusCode) { Console.WriteLine($"Failed to call App2: {htpResponse.StatusCode}"); } response = await htpResponse.Content.ReadAsStringAsync(); } catch (HttpRequestException e) { Console.WriteLine($"HttpRequestException: {e.Message}"); } return appname + " --> " + response; } } }
在App1的appsettings.json中添加配置项:
{ "App2BaseUrl": "http://localhost:2222" }
2. 测试中动态替换App1的配置
修改测试类的SetUp方法,获取App2测试服务器的实际地址,注入到App1的测试配置中:
[SetUp] public void SetUp() { _app2Factory = new WebApplicationFactory<App2.Program>(); _app2Client = _app2Factory.CreateClient(); // 获取App2测试服务器的真实运行地址(随机端口) var app2BaseAddress = _app2Factory.Server.BaseAddress.ToString().TrimEnd('/'); // 配置App1测试服务器,替换App2地址配置 _app1Factory = new WebApplicationFactory<App1.Program>() .WithWebHostBuilder(builder => { builder.ConfigureAppConfiguration((context, config) => { config.AddInMemoryCollection(new Dictionary<string, string> { {"App2BaseUrl", app2BaseAddress} }); }); }); _app1Client = _app1Factory.CreateClient(); }
3. 修正App2控制器的命名空间
将App2控制器的命名空间从App1.Controllers改为App2.Controllers,确保路由匹配:
namespace App2.Controllers { [ApiController] [Route("[controller]")] public class SecondController : ControllerBase { [HttpGet("appname")] public async Task<string> GetAppName() { var appname = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; return appname; } } }
验证
修改完成后重新运行测试:
App2_IsCalled_ReturnsAppName会正常通过,直接调用App2测试客户端无问题。App1_callsApp2_ReturnNamesOfBothAppsConcatenated会正常通过,App1已指向App2测试服务器的真实地址,不再因硬编码端口导致连接失败。
内容的提问来源于stack exchange,提问作者cembo
相关产品推荐
相关产品推荐

