我有一个.NET 6应用程序(带有ASP.NET核心web部件).我使用cypress进行E2E测试.在我们迁移到之后.净额6自.NET框架,我想在web应用程序上运行E2E cypress测试.

我的过程是在C中实际运行nUnit测试,初始化内存中的SQLite数据库,然后将此数据库连接用于我的应用程序.我使用这种方法在实际测试运行之前创建E2E测试所需的数据.然后我从C#单元测试运行cypress.这一切都很顺利.NET Framework,web应用程序实际上是在测试期间运行的,因此它在IIS中的真实URL上可用.

具有NET 6,我们有WebApplicationFactory class,允许在内存中运行应用程序.然而,我希望我的web应用程序可以在一个带有真实端口的真实URL下使用,这样我就可以针对它启动cypress测试.

在探索了this GitHub discussion多个 idea 之后,我创建了一个真正的测试应用程序工厂(具有真正的IP和端口):

public abstract class RealTestServerWebAppFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    private bool _disposed;
    private IHost _host;

    public RealTestServerWebAppFactory()
    {
        ClientOptions.AllowAutoRedirect = false;
        ClientOptions.BaseAddress = new Uri("http://localhost");
    }
    
    public string ServerAddress
    {
        get
        {
            EnsureServer();
            return ClientOptions.BaseAddress.ToString();
        }
    }
    
    public override IServiceProvider Services
    {
        get
        {
            EnsureServer();
            return _host!.Services!;
        }
    }
    
    // Code created with advices from https://github.com/dotnet/aspnetcore/issues/4892
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        base.ConfigureWebHost(builder);
        
        // Final port will be dynamically assigned
        builder.UseUrls("http://127.0.0.1:0");
        
        builder.ConfigureServices(ConfigureServices);
    }
    
    protected override IHost CreateHost(IHostBuilder builder)
    {
        // Create the host for TestServer now before we
        // modify the builder to use Kestrel instead.
        var testHost = builder.Build();

        // Modify the host builder to use Kestrel instead
        // of TestServer so we can listen on a real address.
        builder.ConfigureWebHost(webHostBuilder => webHostBuilder.UseKestrel());

        // Create and start the Kestrel server before the test server,
        // otherwise due to the way the deferred host builder works
        // for minimal hosting, the server will not get "initialized
        // enough" for the address it is listening on to be available.
        // See https://github.com/dotnet/aspnetcore/issues/33846.
        _host = builder.Build();
        _host.Start();

        // Extract the selected dynamic port out of the Kestrel server
        // and assign it onto the client options for convenience so it
        // "just works" as otherwise it'll be the default http://localhost
        // URL, which won't route to the Kestrel-hosted HTTP server.
        var server = _host.Services.GetRequiredService<IServer>();
        var addresses = server.Features.Get<IServerAddressesFeature>();

        ClientOptions.BaseAddress = addresses!.Addresses
            .Select(x => new Uri(x))
            .Last();

        // Return the host that uses TestServer, rather than the real one.
        // Otherwise the internals will complain about the host's server
        // not being an instance of the concrete type TestServer.
        // See https://github.com/dotnet/aspnetcore/pull/34702.
        testHost.Start();
        return testHost;
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);

        if (!_disposed)
        {
            if (disposing)
            {
                _host?.Dispose();
            }

            _disposed = true;
        }
    }

    protected abstract void ConfigureServices(IServiceCollection services);

    /// <summary>
    /// Makes sure that the server has actually been run.
    /// It must be executed at lest once to have the address and port assigned and the app fully working
    /// </summary>
    private void EnsureServer()
    {
        if (_host is null)
        {
            using var _ = CreateDefaultClient();
        }
    }
}

然后我创建了一个从RealTestServerWebAppFactory派生的类,在这里我设置了一些测试依赖注入,但这对我们的示例并不重要.让我们把这个子元素叫做MyServerTestApp班.

测试中的实例创建如下所示:

var serverTestApp = new MyServerTestApp();
serverTestApp.ServerAddress;

在第二行,调用EnsureServer()方法,为应用程序创建默认客户端.它还调用RealTestServerWebAppFactory类中的CreateHost(IHostBuilder builder)来完成所有的魔法.在本地,在这一行之后,我有了应用程序的URL,我可以通过web浏览器(或E2E测试)访问它.

现在,问题是测试在本地运行良好.然而,更准确地说,我也在CI-TeamCity上执行它们.TeamCity用于运行这些测试的dotnet test命令如下所示:

"C:\Program Files\dotnet\dotnet.exe" test C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\E2ETests\bin\Release\net6.0-windows\MyApp.E2ETests.dll /logger:logger://teamcity /TestAdapterPath:C:\TeamCity\buildAgent\plugins\dotnet\tools\vstest15 /logger:console;verbosity=normal

实际上,dotnet test命令的使用非常简单.但是,TeamCity未能执行这些测试,以下情况除外:

[12:47:54][dotnet test] A total of 1 test files matched the specified pattern.
[12:47:56][dotnet test] NUnit Adapter 4.2.0.0: Test execution started
[12:47:56][dotnet test] Running all tests in C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\E2ETests\bin\Release\net6.0-windows\MyApp.E2ETests.dll
[12:47:58][dotnet test]    NUnit3TestExecutor discovered 10 of 10 NUnit test cases using Current Discovery mode, Non-Explicit run
[12:48:07][dotnet test] Setup failed for test fixture MyApp.E2ETests.SomeTestsClass
[12:48:07][dotnet test] System.InvalidOperationException : Sequence contains no elements
[12:48:07][dotnet test] StackTrace:    at System.Linq.ThrowHelper.ThrowNoElementsException()
[12:48:07][dotnet test]    at System.Linq.Enumerable.Last[TSource](IEnumerable`1 source)
[12:48:07][dotnet test]    at MyApp.E2ETests.Infrastructure.RealTestServerWebAppFactory`1.CreateHost(IHostBuilder builder) in C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\MyApp.E2ETests\Infrastructure\RealTestServerWebAppFactory.cs:line 85
[12:48:07][dotnet test]    at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.ConfigureHostBuilder(IHostBuilder hostBuilder)
[12:48:07][dotnet test]    at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
[12:48:07][dotnet test]    at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
[12:48:07][dotnet test]    at MyApp.E2ETests.Infrastructure.RealTestServerWebAppFactory`1.EnsureServer() in C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\MyApp.E2ETests\Infrastructure\RealTestServerWebAppFactory.cs:line 122
[12:48:07][dotnet test]    at MyApp.E2ETests.Infrastructure.RealTestServerWebAppFactory`1.get_ServerAddress() in C:\TeamCity\buildAgent\work\c9a5fb5bca7f6666\Tests\MyApp.E2ETests\Infrastructure\RealTestServerWebAppFactory.cs:line 35
System.InvalidOperationException : Sequence contains no elements

似乎该代码:

var addresses = server.Features.Get<IServerAddressesFeature>();

返回TeamCity正在运行的地址的空连接.因此,该代码:

ClientOptions.BaseAddress = addresses!.Addresses
            .Select(x => new Uri(x))
            .Last();

System.InvalidOperationException : Sequence contains no elements失败.

奇怪的是,如果我使用与TeamCity手动使用cmd相同的dotnet test命令来运行这些测试,它就会正常工作.它在同一台CI机器上工作,但从cmd开始手动执行,而不是TeamCity的构建步骤.CI计算机运行Windows Server 2019.

我一直在想,这可能与用于运行TeamCity的用户的权限有关.然而,TeamCity的Windows服务使用"本地系统"帐户,因此据我所知,它应该拥有所有权限.

感谢您的任何帮助/ idea :)

推荐答案

根据this answer,您必须使用以下代码将默认地址设置为IServerAddressesFeature:

var serverFeatures = featureCollection.Get<IServerAddressesFeature>();
if (serverFeatures.Addresses.Count == 0)
{
    ListenOn(DefaultAddress); // Start the server on the default address
    serverFeatures.Addresses.Add(DefaultAddress) // Add the default address to the IServerAddressesFeature
}

Csharp相关问答推荐

try 还原包时出错:Dapper已经为System.Data.SQLClient定义了依赖项''''

迭代C#List并在数据库中 for each 元素执行SELECT语句—更有效的方式?

C#中使用BouncyCastle计算CMac

为什么C#Bigbit不总是相同的比特长度?

在C#WinUI中,一个关于System的崩溃."由于未知原因导致执行不例外"

ASP.NET Core 8.0 JWT验证问题:尽管令牌有效,但SecurityTokenNoExpirationError异常

如何在NodaTime中为Instant添加一年?

无法将生产环境的AppDbContext设置替换为用于集成测试的内存数据库

如何在onNext之前等待订阅者完成?

未在Windows上运行的Maui项目

如何从Azure函数使用Graph API(SDK 5.35)中的[FindMeetingTimes]

C#中类库项目的源代码生成器

如何正确处置所有动态控件?

仅在ASP.NETCore应用程序中的附加单独端口上公开一组终结点

使用Try-Catch-Finally为API端点处理代码--有什么缺点?

如何使用moq和xUnit对删除操作进行单元测试?

C#如何将SQL查询转换为Lambda表达式

如何使用c#获取Azure中的服务列表?

更改选项卡页面图标 colored颜色

如何使用MediaInfo库在C#/.NET中提取封面艺术?