ASP.NET Core 运行两个 TestServer 用于集成测试

20

我正在尝试运行一些关于令牌管理 API 的集成测试。此外,该 API 还需要令牌发行者 API 也在运行中。

总的来说,我的集成测试需要同时运行 IdentityServer4 Web/API 和 Management API。但是当我创建两个 TestServer 实例时,它们似乎都以相同的 BaseAddress (http://localhost) 结束。


总的来说,我的集成测试需要同时运行 IdentityServer4 Web/API 和 Management API。但是当我创建两个 TestServer 实例时,它们似乎都以相同的 BaseAddress (http://localhost) 结束。
private readonly TestServer _identityTestServer;
private readonly TestServer _mgmtTestServer;
private readonly AppMgmtConfig _config;
private readonly AdminClient _adminClient;
private const string _certificatePassword = "test";

public AdminClientTests() : base(nameof(AdminClientTests))
{
    var connectionString = GetConnectionString();
    var dbSettings = new DbSettings(connectionString);

    Environment.SetEnvironmentVariable("IdentityServer4ConnString",
        dbSettings.IdentityServerConnectionString);
    Environment.SetEnvironmentVariable("CertificatePassword", _certificatePassword);

    _identityTestServer = new TestServer(new WebHostBuilder()
        .UseStartup<USBIdentityServer.Startup>()
        .UseEnvironment("IntegrationTest"));
    USBIdentityServer.Program.InitializeDatabase(_identityTestServer.Host);

    _mgmtTestServer = new TestServer(new WebHostBuilder()
        .UseStartup<IdentityServer4.Management.Startup>()
        .UseEnvironment("IntegrationTest"));

    _config = GetConfig();
    _adminClient = new AdminClient(_config);
}

注意: 我已尝试的事项:

  • 添加.UseUrls("http://localhost:5001")以查看TestServer是否在该端口上运行。
  • 添加serverName.BaseAddress = new Uri("http://localhost:5001");以查看TestServer是否在该端口上运行。

这两个似乎都没有影响。


你有找到关于这个问题的任何信息吗?我也遇到了非常类似的问题。参考链接:https://stackoverflow.com/questions/47812182/aspnetcore-testhost-ignoring-usemvc-in-an-nunit-integration-test-in-rider - Yuri Zolotarev
很遗憾,我没有。 - blgrnboy
你如何与这些测试服务器进行交互?就像我的集成测试只使用一个服务器,我是通过从myTestServer.CreateClient()获取的HttpClient来调用API。因此,测试服务器和(测试)客户端之间似乎存在连接 - superjos
我遇到了同样的问题。 - Scott Bamforth
1
我曾经遇到过同样的问题,你提供的建议UseUrls("http://localhost:5001")和Add serverName.BaseAddress = new Uri("http://localhost:5001")对我有用。不过我使用的是.NET Core 3.0,可能已经修复了这个问题。 - shelbypereira
4个回答

9
我知道这是一个老问题,但我也遇到了同样的问题。我认为诀窍是将从“服务器1”获得的HttpClient注入“服务器2”中。

来自ASP.NET问题:

测试服务器不会打开任何真实的套接字,因此在不同的URL上运行是一个虚构的概念。您可以将BaseAddress设置为任何您想要的值。

我假设您实际的挑战是与这些测试服务器通信?同样,它们不会打开任何真实的套接字,与它们通信的唯一方法是通过它们的CreateClient API创建一个内存通道。

示例(.NET Core):

//Start and configure server 1.
IWebHostBuilder server1 = new WebHostBuilder()
 .UseStartup < Project1.Startup > ()
 .UseKestrel(options => options.Listen(IPAddress.Any, 80))
 .UseConfiguration(new ConfigurationBuilder()
  .AddJsonFile("appsettings_server1.json")
  .Build()
 );

TestServer testServer1 = new TestServer(server1);

//Get HttpClient that's able to connect to server 1.
HttpClient client1 = server1.CreateClient();

IWebHostBuilder _server2 = new WebHostBuilder()
 .UseStartup < Project2.Startup > ()
 .ConfigureTestServices(
  services => {
   //Inject HttpClient that's able to connect to server 1.
   services.AddSingleton(typeof(HttpClient), client1);
  })
 .UseKestrel(options => options.Listen(IPAddress.Any, 81))
 .UseConfiguration(new ConfigurationBuilder()
  .AddJsonFile("appsettings_server2.json")
  .Build()
 );

TestServer testServer2 = new TestServer(server2);

你有没有想法如何处理3个测试服务器,其中需要注入2个HttpClients以将它们用于不同的微服务请求? - Rasul
由于我们没有三个测试服务器,我没有花太多时间来做这件事,很抱歉要说,但我不知道如何完成这个任务。 - Sander

1
这可能是一个不好的设计,但我认为值得在那个问题上提一下 - 在单独的进程中启动测试过的服务

以NUnit为例:

[SetUpFixture]
public class GlobalSetUpFixture 
{
    private static Process firstServiceProcess;
    private static Process secondServiceProcess;
    
    [OneTimeSetUp]
    public async Task RunBeforeTests()
    {
        var firstPath = "path/to/first/csproj";
        var secondPath = "path/to/second/csproj";

        var firstCommandArguments = $"run -p {firstPath}\\FirstProject.csproj --launch-profile TestsEnvironment";
        var secondCommandArguments = $"run -p {secondPath}\\SecondProject.csproj --launch-profile TestsEnvironment";

        var firstProcessInfo = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            UseShellExecute = true,
            FileName = "dotnet",
            Arguments = firstCommandArguments 
        };
        var secondProcessInfo = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            UseShellExecute = true,
            FileName = "dotnet",
            Arguments = secondCommandArguments
        };

        firstServiceProcess = Process.Start(firstProcessInfo);
        secondServiceProcess = Process.Start(secondProcessInfo);
    }

    [OneTimeTearDown]
    public async Task RunAfterTests()
    {
        firstServiceProcess?.Kill();
        secondServiceProcess?.Kill();
    }
}

这个解决方案可能有以下几种用途:
  • 需要同时运行多个服务(例如,它们之间存在相互依赖)。
  • 作为依赖注入HttpClient、IHttpClientFactory和其他连接(如SignalR/WebSockets)的替代方法。
  • 服务使用实际的套接字运行,可以相互通信。
  • 与使当前项目运行测试相比,这可能是一种更简单的方法。

1
我正在使用.NET Core 3.1并扩展了@Sander提供的答复。
这里有适用于2个微服务(Adapter和Objects)的工作解决方案。我想从Adapter服务调用Objects API。
如果您需要多个客户端,则应将其包装在特定的客户端包装器中(在我的情况下为ObjectsApiClient),设置正确的BaseAddress,然后它应该可以工作。
// Adapter microservice - Objects microservice client wrapper

public class ObjectsApiClient
{
    private readonly HttpClient _httpClient;

    public ObjectsApiClient(
        HttpClient httpClient,
        IAuthorizationService authorizationService, // additional services you need in your client wrapper
        IHttpHeaderAppService httpHeaderAppService)
    {
        _httpClient = httpClient;
        ...
    }
}

// Adapter microservice - Startup class 

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddHttpClient<ObjectsApiClient>(client =>
        {
            client.BaseAddress = new Uri("http://localhost/api/v1/objects/");
        })
}

// Adapter microservice integration test class

private IHost _testServer;
private IHost _objectsTestServer;

// Adapter microservice integration test constructor

_objectsTestServer = new HostBuilder()
    .ConfigureWebHost(webHost =>
        webHost
            .UseStartup(typeof(Objects.Startup))
            .UseConfiguration(configuration)
            .UseTestServer())
    .Start();

var objectsHttpClient = _objectsTestServer.GetTestClient();
objectsHttpClient.BaseAddress = new Uri("http://localhost/api/v1/objects/");

var objectsApiClient = new ObjectsApiClient(
    objectsHttpClient,
    _objectsTestServer.Services.GetService<IAuthorizationService>(),    // inject services you need in your client wrapper
    _objectsTestServer.Services.GetService<IHttpHeaderAppService>());   

_testServer = new HostBuilder()
    .ConfigureWebHost(webHost =>
        webHost
            .UseStartup(typeof(Startup))
            .UseConfiguration(configuration)
            .ConfigureTestServices(config => config
                .AddSingleton(typeof(ObjectsApiClient), objectsApiClient)) // you can register other typed clients here
            .UseTestServer())
    .Start();

0
所有之前的解决方案都是可行的,但它们用了错误的方法来做正确的事情。 不应该在 DI 中注入 HttpClient 或某种包装器,而应该使用 IHttpClientFactory 配置 HttpClient。假设您已经配置了 2 个测试服务器:testServer1testServer2,并且想从第三个测试服务器调用它们:
.ConfigureServices(services =>
{
    services
        .AddHttpClient<TestServer1Client>()
        .ConfigurePrimaryHttpMessageHandler(() => testServer1.CreateHandler());

    services
        .AddHttpClient<TestServer2Client>()
        .ConfigurePrimaryHttpMessageHandler(() => testServer2.CreateHandler());
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接