Escribe pruebas con Testcontainers
Las pruebas existentes utilizan una base de datos SQLite en memoria. Aunque es conveniente, esto no coincide con el comportamiento de producción. Puedes reemplazarla con una instancia real de Microsoft SQL Server gestionada por Testcontainers.
Agrega dependencias
Cambia al directorio del proyecto de pruebas y agrega el proveedor de Entity Framework para SQL Server y el módulo MSSQL de Testcontainers:
$ cd tests/RazorPagesProject.Tests
$ dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 7.0.0
$ dotnet add package Testcontainers.MsSql --version 3.0.0
NoteTestcontainers para .NET ofrece una gama de módulos que siguen las configuraciones de mejores prácticas.
Crea la clase de prueba
Crea un archivo MsSqlTests.cs en el directorio IntegrationTests. Esta clase gestiona el ciclo de vida del contenedor SQL Server y contiene una clase de prueba anidada.
using System.Data.Common;
using System.Net;
using AngleSharp.Html.Dom;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using RazorPagesProject.Data;
using RazorPagesProject.Tests.Helpers;
using Testcontainers.MsSql;
using Xunit;
namespace RazorPagesProject.Tests.IntegrationTests;
public sealed class MsSqlTests : IAsyncLifetime
{
private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder().Build();
public Task InitializeAsync()
{
return _msSqlContainer.StartAsync();
}
public Task DisposeAsync()
{
return _msSqlContainer.DisposeAsync().AsTask();
}
public sealed class IndexPageTests : IClassFixture<MsSqlTests>, IDisposable
{
private readonly WebApplicationFactory<Program> _webApplicationFactory;
private readonly HttpClient _httpClient;
public IndexPageTests(MsSqlTests fixture)
{
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.AllowAutoRedirect = false;
_webApplicationFactory = new CustomWebApplicationFactory(fixture);
_httpClient = _webApplicationFactory.CreateClient(clientOptions);
}
public void Dispose()
{
_webApplicationFactory.Dispose();
}
[Fact]
public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot()
{
// Arrange
var defaultPage = await _httpClient.GetAsync("/")
.ConfigureAwait(false);
var document = await HtmlHelpers.GetDocumentAsync(defaultPage)
.ConfigureAwait(false);
// Act
var form = (IHtmlFormElement)document.QuerySelector("form[id='messages']");
var submitButton = (IHtmlButtonElement)document.QuerySelector("button[id='deleteAllBtn']");
var response = await _httpClient.SendAsync(form, submitButton)
.ConfigureAwait(false);
// Assert
Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("/", response.Headers.Location.OriginalString);
}
private sealed class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
private readonly string _connectionString;
public CustomWebApplicationFactory(MsSqlTests fixture)
{
_connectionString = fixture._msSqlContainer.GetConnectionString();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.Remove(services.SingleOrDefault(service => typeof(DbContextOptions<ApplicationDbContext>) == service.ServiceType));
services.Remove(services.SingleOrDefault(service => typeof(DbConnection) == service.ServiceType));
services.AddDbContext<ApplicationDbContext>((_, option) => option.UseSqlServer(_connectionString));
});
}
}
}
}Entiende la estructura de la prueba
Ciclo de vida del contenedor con IAsyncLifetime
La clase externa MsSqlTests implementa IAsyncLifetime. xUnit llama a InitializeAsync() justo después de crear la instancia de la clase, lo que inicia el contenedor de SQL Server. Después de que se completan todas las pruebas, DisposeAsync() detiene y elimina el contenedor.
private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder().Build();MsSqlBuilder().Build() crea un contenedor de Microsoft SQL Server preconfigurado. Los módulos de Testcontainers siguen las mejores prácticas, por lo que no necesitas configurar puertos, contraseñas ni estrategias de espera de inicio por ti mismo.
Clase de prueba anidada con IClassFixture
La clase IndexPageTests está anidada dentro de MsSqlTests e implementa IClassFixture<MsSqlTests>. Esto le da a la clase de prueba acceso al campo privado del contenedor y crea una jerarquía limpia en el explorador de pruebas.
Custom WebApplicationFactory
En lugar de utilizar la fábrica basada en SQLite, la CustomWebApplicationFactory anidada recupera la cadena de conexión del contenedor SQL Server en ejecución y la pasa a UseSqlServer():
private sealed class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
private readonly string _connectionString;
public CustomWebApplicationFactory(MsSqlTests fixture)
{
_connectionString = fixture._msSqlContainer.GetConnectionString();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.Remove(services.SingleOrDefault(service => typeof(DbContextOptions<ApplicationDbContext>) == service.ServiceType));
services.Remove(services.SingleOrDefault(service => typeof(DbConnection) == service.ServiceType));
services.AddDbContext<ApplicationDbContext>((_, option) => option.UseSqlServer(_connectionString));
});
}
}Esta fábrica:
- Elimina el registro existente de
DbContextOptions<ApplicationDbContext> - Elimina el registro existente de
DbConnection - Agrega un nuevo
ApplicationDbContextconfigurado con la cadena de conexión de SQL Server del contenedor gestionado por Testcontainers
NoteLa imagen Docker de Microsoft SQL Server no es compatible con dispositivos ARM, como los Mac con Apple Silicon. Puedes usar el módulo SqlEdge o Testcontainers Cloud como alternativas.