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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 15:44:54