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

ASP.NET Core 8测试中启动WebApplication监听随机端口失败如何修复?

问题根源

你在GetServerHost中调用了builder.UseTestServer(),而TestServer是ASP.NET Core提供的内存测试服务器——它不会绑定真实的网络端口,仅在内存中模拟HTTP请求处理逻辑,所以你后续通过UseUrls设置的端口会被TestServer直接忽略,自然不会监听指定端口。

修复方案

根据你的测试需求,分两种场景给出具体修复:

场景1:需要真实监听随机端口(用于外部客户端/服务调用)

如果测试需要外部服务或真实客户端连接,必须移除TestServer,使用真实的Kestrel服务器:

1. 修改GetServerHost方法

移除UseTestServer()调用,因为我们需要真实的HTTP服务器:

static member GetServerHost<'EntryPoint> () =
    let assembly = typeof<'EntryPoint>.Assembly
    let deferredHostBuilder =
        DeferredHostBuilder
            .Create()
            .UseEnvironment(Environments.Development)
            .ConfigureHostConfiguration (fun config ->
                config.AddInMemoryCollection (
                    kvp HostDefaults.ApplicationKey (assembly.GetName().Name)
                    |> Seq.singleton
                )|> ignore
            )

    let configureHostBuilder = DeferredHostBuilder.ConfigureHostBuilder deferredHostBuilder
    let entrypointCompleted = DeferredHostBuilder.EntryPointCompleted deferredHostBuilder
    let func =
        HostFactoryResolver.ResolveHostFactory (
            assembly,
            Nullable (),
            Nullable false,
            configureHostBuilder,
            entrypointCompleted
        )

    DeferredHostBuilder.SetHostFactory deferredHostBuilder func
    // 移除UseTestServer(),使用默认的Kestrel服务器
    deferredHostBuilder.ConfigureWebHostDefaults (fun builder -> ())

2. 自动分配随机端口(避免冲突)

不用手动生成随机数,让ASP.NET Core自动分配可用端口,启动后再获取实际绑定的端口:

[<TestInitialize>]
member this.TestInitialize () =
    let serverBuilder =
        ConsoleUserAuthIntegrationTests
            .GetServerHost<Server.Program.App>()
            .ConfigureWebHost (fun builder ->
                // 用http://*:0让系统自动分配随机端口
                builder.UseUrls ("http://*:0") |> ignore
            )
    this.serverHost <- serverBuilder.Build ()
    // 启动主机
    this.serverHost.RunAsync () |> ignore
    // 获取实际绑定的端口
    let serverAddresses = this.serverHost.Services.GetRequiredService<IServerAddressesFeature>().Addresses
    let actualPort = Uri(serverAddresses.First()).Port
    // 后续可使用actualPort进行客户端调用

场景2:仅测试内部服务(不需要真实端口)

如果只是测试内部服务(比如你代码中的OpenIddictClientService),可以继续使用TestServer,但无需设置端口,直接通过TestServer获取测试客户端或服务实例:

[<TestInitialize>]
member this.TestInitialize () =
    let serverBuilder =
        ConsoleUserAuthIntegrationTests
            .GetServerHost<Server.Program.App>()
    this.serverHost <- serverBuilder.Build ()
    this.serverHost.StartAsync () |> ignore
    
    // 获取TestServer的测试客户端(用于模拟HTTP请求)
    let testServer = this.serverHost.Services.GetRequiredService<TestServer>()
    this.testClient <- testServer.CreateClient()
    
    // 直接获取内部服务实例
    this.openIddictClient <- this.serverHost.Services.GetRequiredService<OpenIddictClientService>()
额外建议

你通过反射调用的DeferredHostBuilder是Microsoft.AspNetCore.Mvc.Testing的内部非公开类型,后续版本可能存在兼容性问题。更推荐直接继承WebApplicationFactory<TEntryPoint>来实现自定义配置:

type CustomTestFactory<'TEntryPoint when 'TEntryPoint :> obj>() =
    inherit WebApplicationFactory<'TEntryPoint>()

    override this.ConfigureWebHost(builder) =
        base.ConfigureWebHost(builder)
        // 在这里自定义配置,比如替换测试服务、修改环境变量等
        builder.ConfigureServices(fun services ->
            // 示例:替换服务为测试实现
            services.Replace(ServiceDescriptor.Scoped<IMyService, TestMyService>())
        )
        // 如果需要真实端口,添加以下代码(移除TestServer):
        // builder.UseKestrel() |> ignore

在测试中使用该工厂:

[<TestInitialize>]
member this.TestInitialize () =
    let factory = CustomTestFactory<Server.Program.App>()
    // 获取真实服务器端口(如果配置了UseKestrel)
    let actualPort = Uri(factory.ServerAddresses.First()).Port
    // 获取测试客户端
    this.testClient <- factory.CreateClient()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 07:19:56