How do I send a request to an in-memory TestServer in ASP.NET during testing?

55 Views Asked by At

I am trying to implement an endpoint that acts as a proxy to another endpoint.

Suppose I have the following:

// List Released Projects
[HttpGet("Projects")]
public async Task<ActionResult<List<ProjectAggrDto>>> GetReleasedProjectsAsync(
    CancellationToken cancellationToken = default)
{
    var query = new GetReleasedProjectsQuery();
    return await _sender.Send(query, cancellationToken);
}

// List Released Projects From Central
[HttpGet("ProjectsFromCentral")]
public async Task<ActionResult<List<ProjectAggrDto>>> GetReleasedProjectsFromCentralAsync(
    CancellationToken cancellationToken = default)
{
    var authorizationHeader = Request.Headers[HeaderNames.Authorization].ToString();
    var query = new GetReleasedProjectsFromCentralQuery(authorizationHeader);
    return await _sender.Send(query, cancellationToken);
}

So the second endpoint would read an option entry from appsettings.json to get the 'central' server IP, and would use an IHttpClientFactory to create a simple HTTP client to communicate with the server, and would just ultimately call the first endpoint, and returning that data back to the user.

To reiterate,

  1. User send a request to /api/ProjectsFromCentral
  2. GetReleasedProjectsFromCentralAsync will create a HTTP client to pass on the request to the first endpoint with the central server's IP: {central_server_ip}/api/Projects
  3. GetReleasedProjectsFromCentralAsync returns the response of GetReleasedProjectsAsync

Now, I tried this out with Postman - and it does work as intended. However, I'd like to use this in an integration test. And the integration test uses a TestServer which create an in-memory server instance - however, TestServer's base address of http://localhost/ is not reachable.

public class EnvironmentFixure : IAsyncLifetime
{
    private IDockerEnvironment? _environment;
    private WebApplicationFactory<Program>? _application;
    private string? _connectionString;
    private string? _redisConnectionString;

    public string ServerUrl { get; private set; }

    public WebApplicationFactory<Program> WebApplication { get => _application ?? throw new Exception("Not initialized"); }


    public EnvironmentFixure()
    {
    }

    public async Task InitializeAsync()
    {
        _environment = new DockerEnvironmentBuilder()
            .AddContainer(p => p with
            {
                Name = "test-minio",
                ImageName = "minio/minio",
                Ports = new Dictionary<ushort, ushort>
                {
                    { 9000, 9001 },
                    { 9090, 9091 },
                },
                EnvironmentVariables = new Dictionary<string, string>
                {
                    { "MINIO_ROOT_USER", "admin" },
                    { "MINIO_ROOT_PASSWORD", "password" },

                },
                ExposedPorts = new List<ushort> { 9000, 9090 },
                Entrypoint = new List<string> { "minio", "server", "/home/share", "--console-address", ":9090" },
            })
            .AddRedisContainer(r => r with
            {
                Name = "test-redis"
            })
            .AddPostgresContainer(p => p with
            {
                Name = "test-postgres",
            })
            .Build();

        await _environment.UpAsync();

        var minio = _environment.GetContainer("test-minio");
        if (minio is not null)
        {
            var result = await minio.ExecAsync(new string[] { "mc", "mb", "e-exam" });
        }

        var postgres = _environment.GetContainer<PostgresContainer>("test-postgres");
        _connectionString = postgres?.GetConnectionString()
            ?? throw new ApplicationException("ConnectionString not specified");

        var redis = _environment.GetContainer<RedisContainer>("test-redis");
        var redisConnectionConfiguration = redis?.GetConnectionConfiguration()
            ?? throw new ApplicationException("Redis container is not initialized");

        _redisConnectionString = $"{redisConnectionConfiguration.Host}:{redisConnectionConfiguration.Port},password={redisConnectionConfiguration.Password}";

        _application = new MyWebApplicationFactory
        {
            ConnectionString = _connectionString,
            RedisConnectionString =  _redisConnectionString,
        };

        // ----- HERE --------
        ServerUrl = _application.Server.BaseAddress.ToString(); // This returns "http://localhost/", but it doesn't work (I can't connect).
        // -------------------

        using var scope = _application.Services.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
        dbContext.Database.Migrate();
    }

    public async Task DisposeAsync()
    {
        await _application!.DisposeAsync();
        await _environment!.DownAsync();
        await _environment!.DisposeAsync();
    }

    private class MyWebApplicationFactory : WebApplicationFactory<Program>
    {
        public required string ConnectionString { get; init; }
        public required string RedisConnectionString { get; init; }

        protected override IHost CreateHost(IHostBuilder builder)
        {
            builder.ConfigureHostConfiguration(config =>
            {
                config.AddJsonFile("appsettings.Test.json", optional: false);
                config.AddEnvironmentVariables();
                config.AddInMemoryCollection(new Dictionary<string, string>
                {
                    ["ConnectionStrings:DefaultConnection"] = ConnectionString,
                    ["RedisConfig:ConnectionString"] = RedisConnectionString,
                }!);

            });

            return base.CreateHost(builder);
        }
    }
}

So during tests, I wanted the central_server_ip to be the IP of the created WebApplication.

Therefore, the tests are failing since it could not reach the "central" server (which in this instance, itself). What can I do here?

I expected the TestServer URL of http://localhost/ to be reachable, but it does not. Therefore, I cannot test if the second endpoint can call the first endpoint.

0

There are 0 best solutions below