【问题标题】:ASP.NET Core running two TestServer for Integration TestingASP.NET Core 运行两个 TestServer 进行集成测试
【发布时间】:2017-11-02 18:05:15
【问题描述】:

我正在尝试为令牌管理 API 运行一些集成测试。该 API 还需要运行令牌颁发者 API。

总之,我的集成测试需要同时运行 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 是否会在该端口上运行。

这些似乎都不会影响它。

【问题讨论】:

  • 你有没有找到任何关于这方面的信息我实际上遇到了一个非常相似的问题stackoverflow.com/questions/47812182/…
  • 很遗憾我没有
  • 您如何与这些测试服务器交互?在我只使用一台服务器的集成测试中,我正在使用从myTestServer.CreateClient() 获得的HttpClient 执行我的 API。所以看起来测试服务器和(测试)客户端之间有一个连接
  • 我也有同样的问题
  • 我有同样的问题,你的建议 .UseUrls("localhost:5001") 和 Add serverName.BaseAddress = new Uri("localhost:5001")worked 对我来说,但是我使用的是 .NET Core 3.0 所以也许问题已解决。

标签: c# asp.net-core integration-testing


【解决方案1】:

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

From ASP.NET issues:

测试服务器没有打开任何真正的套接字,所以在不同的 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 个 TestServers,你需要在其中注入 2 个 HttpClients 以将它们用于不同的微服务请求吗?
  • 由于我们没有 3 个测试服务器,所以我没有花太多时间来做这件事,很抱歉,但我不知道如何做到这一点。
【解决方案2】:

我正在开发 .NET Core 3.1,并扩展了 @Sander 提供的回复。

这里有 2 个微服务的有效解决方案:适配器和对象。 我想从 Adapter 服务调用 Objects API。

如果您需要超过 1 个客户端,则应将其包装在特定的客户端包装器中(在我的情况下为 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();

【讨论】:

    【解决方案3】:

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

    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) 等其他连接的替代方法。
    • 服务使用真实的套接字运行,并且可以相互通信。
    • 与调整当前项目以运行测试相比,这可能是一种更简单的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-06-16
      • 2020-03-04
      • 2018-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-28
      • 1970-01-01
      相关资源
      最近更新 更多