Skip to content

Server

Las clases Server<TProgram> y ServerBase<TProgram> proporcionan un entorno de prueba integral para aplicaciones ASP.NET Core, especialmente microservicios. Estas clases simplifican la creación, configuración y gestión de un servidor web para pruebas unitarias y de integración, ofreciendo una base sólida para validar el comportamiento de tus aplicaciones de manera eficiente y confiable.

¿Cómo Funciona?


Server<TProgram> extiende WebApplicationFactory<TProgram>, proporcionando un punto de entrada para la configuración y el manejo del ciclo de vida del servidor de pruebas. ServerBase<TProgram>, por otro lado, proporciona una clase base para las pruebas que se ejecutan sobre el servidor creado por Server<TProgram>, facilitando el acceso al cliente HTTP, al proveedor de servicios y al canal gRPC.

El flujo general de funcionamiento es el siguiente:

  1. Configuración del Host: Server<TProgram> configura el host web de prueba, estableciendo las configuraciones de la aplicación, los servicios y el entorno. Utiliza un ServerCompose para la gestión de dependencias externas, como Redis, RabbitMQ, MongoDB y OpenTelemetry, a través de Docker Compose.
  2. Inicialización de Dependencias: El ServerCompose inicia los contenedores de Docker necesarios para las pruebas, asegurando que las dependencias externas estén disponibles.
  3. Creación del Cliente: ServerBase<TProgram> utiliza el método CreateClient de Server<TProgram> para crear un HttpClient que puede utilizarse para enviar solicitudes al servidor de prueba.
  4. Acceso al Servicio: ServerBase<TProgram> proporciona acceso al IServiceProvider del servidor de prueba, permitiendo obtener instancias de servicios configurados en el mismo.
  5. Acceso al Logger: ServerBase<TProgram> proporciona acceso al InMemoryLoggerProvider, permitiendo inspeccionar los logs que genera el servidor de prueba.
  6. Creación del Canal gRPC: ServerBase<TProgram> crea un canal gRPC utilizando el HttpHandler del servidor, lo que permite realizar llamadas gRPC.
  7. Ejecución de Pruebas: Las pruebas se ejecutan utilizando ServerBase<TProgram>, que proporciona una API conveniente para interactuar con el servidor de prueba.
  8. Limpieza: Al finalizar las pruebas, los contenedores de Docker y los recursos utilizados se liberan, asegurando un entorno de pruebas limpio.

Server<TProgram>


La clase Server<TProgram> extiende WebApplicationFactory<TProgram> y proporciona un entorno de prueba para aplicaciones ASP.NET Core. Esta clase se encarga de configurar el host web de prueba, gestionar las dependencias externas a través de ServerCompose, y proporcionar un HttpClient para realizar solicitudes HTTP al servidor de pruebas.

ConfigureWebHost

Configura el IWebHostBuilder para el servidor de pruebas. Establece las configuraciones, registra los servicios, y configura el entorno de desarrollo. Inyecta las dependencias levantadas por ServerCompose.

Dispose

Detiene los contenedores de Docker y libera los recursos, como el InMemoryLoggerProvider.

ConfigureServices

Configura los servicios para la aplicación de pruebas, como la autenticación de prueba y el logging en memoria.

ServerBase<TProgram>


La clase ServerBase<TProgram> proporciona una clase base para las pruebas que se ejecutan sobre el servidor de pruebas creado por Server<TProgram>. Esta clase facilita el acceso al HttpClient, al IServiceProvider y al canal gRPC, permitiendo interactuar con el servidor de pruebas de manera eficiente y confiable.

InitializeAsync

Inicializa los recursos necesarios para las pruebas, como el HttpClient, el IServiceProvider y el canal gRPC.

DisposeAsync()

No realiza ninguna acción de limpieza adicional (en esta implementación), dado que la limpieza principal se realiza en el método Dispose de Server<TProgram>.

Implementación


Las clases Server<TProgram> y ServerBase<TProgram> se implementan como sigue:

using CodeDesignPlus.Net.xUnit.Microservice.Server.Authentication;
using CodeDesignPlus.Net.xUnit.Microservice.Server.Logger;
using CodeDesignPlus.Net.xUnit.Microservice.Server.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
namespace CodeDesignPlus.Net.xUnit.Microservice.Server;
/// <summary>
/// A server class for configuring and managing a web application for testing purposes.
/// </summary>
/// <typeparam name="TProgram">The program class of the web application.</typeparam>
public class Server<TProgram> : WebApplicationFactory<TProgram> where TProgram : class
{
/// <summary>
/// Gets the server compose instance for managing external dependencies.
/// </summary>
public ServerCompose Compose { get; } = new();
/// <summary>
/// Gets the in-memory logger provider for capturing log messages.
/// </summary>
public InMemoryLoggerProvider LoggerProvider { get; } = new();
/// <summary>
/// Gets or sets the action to configure the in-memory collection.
/// </summary>
public Action<Dictionary<string, string>> InMemoryCollection { get; set; }
/// <summary>
/// Configures the web host for the application.
/// </summary>
/// <param name="builder">The web host builder.</param>
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
var configurationValues = new Dictionary<string, string>()
{
{"Redis:Instances:Core:ConnectionString", $"{Compose.Redis.Item1}:{Compose.Redis.Item2}"},
{"RabbitMQ:Host", Compose.RabbitMQ.Item1},
{"RabbitMQ:Port", Compose.RabbitMQ.Item2.ToString()},
{"Mongo:ConnectionString", $"mongodb://{Compose.Mongo.Item1}:{Compose.Mongo.Item2}"},
{"Observability:ServerOtel", $"http://{Compose.Otel.Item1}:{Compose.Otel.Item2}"},
{"Logger:OTelEndpoint", $"http://{Compose.Otel.Item1}:{Compose.Otel.Item2}" },
};
InMemoryCollection?.Invoke(configurationValues);
var contentRoot = Path.GetDirectoryName(Assembly.GetAssembly(typeof(TProgram))!.Location);
var configuration = new ConfigurationBuilder()
.SetBasePath(contentRoot!)
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json")
.AddInMemoryCollection(configurationValues)
.Build();
builder.UseConfiguration(configuration);
builder.ConfigureServices(ConfigureServices);
builder.UseEnvironment("Development");
}
/// <summary>
/// Disposes the server and its resources.
/// </summary>
/// <param name="disposing">A boolean value indicating whether the object is being disposed.</param>
protected override void Dispose(bool disposing)
{
Compose.StopInstance();
LoggerProvider.Dispose();
base.Dispose(disposing);
}
/// <summary>
/// Configures the services for the application.
/// </summary>
/// <param name="services">The service collection.</param>
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("TestAuth")
.AddScheme<AuthenticationSchemeOptions, AuthHandler>("TestAuth", options => { });
services.AddSingleton(this.LoggerProvider);
services.AddSingleton<ILoggerFactory, InMemoryLoggerFactory>();
}
}
using CodeDesignPlus.Net.xUnit.Microservice.Server.Logger;
using Grpc.Net.Client;
namespace CodeDesignPlus.Net.xUnit.Microservice.Server;
/// <summary>
/// A base server class for configuring and managing a web application for testing purposes.
/// </summary>
/// <typeparam name="TProgram">The program class of the web application.</typeparam>
/// <param name="server">The server instance.</param>
public class ServerBase<TProgram>(Server<TProgram> server) : IAsyncLifetime where TProgram : class
{
private AsyncServiceScope scope;
/// <summary>
/// Gets the service provider for the application.
/// </summary>
protected IServiceProvider Services;
/// <summary>
/// Gets the HTTP client for making requests to the server.
/// </summary>
protected HttpClient Client;
/// <summary>
/// Gets the in-memory logger provider for capturing log messages.
/// </summary>
protected InMemoryLoggerProvider LoggerProvider => server.LoggerProvider;
/// <summary>
/// Gets the gRPC channel for making gRPC calls to the server.
/// </summary>
protected GrpcChannel Channel;
/// <summary>
/// Initializes the server and its resources asynchronously.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task InitializeAsync()
{
Client = server.CreateClient();
scope = server.Services.CreateAsyncScope();
Services = scope.ServiceProvider;
var options = new GrpcChannelOptions
{
HttpHandler = server.Server.CreateHandler()
};
Channel = GrpcChannel.ForAddress(server.Server.BaseAddress, options);
return Task.CompletedTask;
}
/// <summary>
/// Disposes the server and its resources asynchronously.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task DisposeAsync()
{
return Task.CompletedTask;
}
}

Ejemplo de Uso


El siguiente ejemplo muestra cómo utilizar Server<TProgram> y ServerBase<TProgram> en un método de prueba unitaria:

using CodeDesignPlus.Net.xUnit.Microservice.Server;
namespace CodeDesignPlus.Net.xUnit.Microservice.Test.Server;
public class ServerTest : ServerBase<Program>, IClassFixture<Server<Program>>
{
private readonly Server<Program> server;
private readonly Guid value = Guid.NewGuid();
public ServerTest(Server<Program> server) : base(server)
{
this.server = server;
server.InMemoryCollection = (x) =>
{
x.Add("Custom:Item", value.ToString());
};
}
[Fact]
public async Task GetWeatherForecast_ReturnsWeatherForecast()
{
//Act
var response = await Client.GetAsync("/weatherforecast");
var value = server.Services.GetService<IConfiguration>()?.GetValue<string>("Custom:Item");
// Assert
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
var data = Serializers.JsonSerializer.Deserialize<IEnumerable<Api.WeatherForecast>>(responseString);
Assert.NotNull(data);
Assert.NotEmpty(data);
Assert.Equal(value, this.value.ToString());
}
}

En este ejemplo:

  • ServerTest hereda de ServerBase<Program> y recibe una instancia de Server<Program> a través del constructor.
  • Se utiliza IClassFixture<Server<Program>> para indicar que la prueba necesita un servidor de prueba.
  • La prueba utiliza el HttpClient (Client) para realizar una petición HTTP al endpoint /weatherforecast.
  • Se realizan aserciones sobre la respuesta para verificar que fue exitosa y que se recibieron los datos esperados.

Conclusiones


Server<TProgram> y ServerBase<TProgram> son clases fundamentales para la creación de pruebas de integración y unitarias en aplicaciones .NET. Al proporcionar un entorno de prueba controlado, facilitar la gestión de dependencias externas y ofrecer una API conveniente para interactuar con el servidor de pruebas, estas clases mejoran la eficiencia y confiabilidad del proceso de pruebas.

Referencias Adicionales