我有以下简化的 ASP.net API 控制器来测试:
[Route("api")]
public class CustomerController(Func<DataContext> factory) : Controller
{
[HttpPost("customer")]
public async Task<IActionResult> Create([FromBody] CustomerRequest request)
{
using var context = factory.Invoke();
var uow = new UnitOfWork(context);
await uow.CustomerService.Create(request.ToCustomer());
return Ok();
}
}
下面是我的测试:
[Collection(nameof(ControllerTestCollection))]
public class CustomerApiTest(IntegrationTestFactory factory)
{
private readonly HttpClient client = factory.CreateClient();
[Fact]
public async Task TestCreateCustomer()
{
var response = await client.PostAsJsonAsync("api/customer", new CustomerRequest(1, "Customer 1"));
response.StatusCode.Should().Be(HttpStatusCode.Created);
}
}
下面是我定制的WebApplicationFactory:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices
(
services =>
{
services.RemoveDbContext<DataContext>();
services.AddDbContext<DataContext>(options => options.UseMySql(testcontainer.GetConnectionString()));
services.EnsureDbCreated<DataContext>();
}
);
}
当我运行测试时,控制器继续使用真实的数据库设置,而不是我为此测试设置的设置,因此基本上它会忽略指向 Testcontainers 托管的数据库的 DataContext。但我可以保证传递给测试的自定义 WebApplicationFactory 正确指向测试数据库。
我是 ASP.Net 和 EF Core 的新手,因此我们将不胜感激。
致以诚挚的问候,
设置Nug
WebApplicationFactory<TEntryPoint>
用于创建用于集成测试的 TestServer。 TEntryPoint 是 SUT 的入口点类,通常是 Program.cs。
我已经在本地进行了测试,正确传递了数据上下文
自定义WebApplicationFactory
using System.Data.Common;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram> where TProgram : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
var dbContextDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<DataContext>));
services.Remove(dbContextDescriptor!);
var dbConnectionDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbConnection));
services.Remove(dbConnectionDescriptor!);
// Create open SqliteConnection so EF won't automatically close it.
services.AddSingleton<DbConnection>(container =>
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
return connection;
});
services.AddDbContext<DataContext>((container, options) =>
{
var connection = container.GetRequiredService<DbConnection>();
options.UseSqlite(connection);
});
});
builder.UseEnvironment("Development");
}
}
测试
public class CustomerApiTest(CustomWebApplicationFactory<Program> factory) : **IClassFixture<CustomWebApplicationFactory<Program>>**
{
private readonly HttpClient client = factory.CreateClient();
[Fact]
public async Task TestCreateCustomer()
{
var response = await client.GetAsync("/WeatherForecast");
}
}
数据库上下文
using Microsoft.EntityFrameworkCore;
public class DataContext(DbContextOptions<DataContext> options) : DbContext(options)
{
public virtual DbSet<Message> Messages { get; set; }
}
控制器
using Microsoft.AspNetCore.Mvc;
namespace WebApplication.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController(ILogger<WeatherForecastController> logger, DataContext context) : ControllerBase
{
private static readonly string[] Summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
private readonly ILogger<WeatherForecastController> _logger = logger;
private readonly DataContext context = context;
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
var messages = context.Messages.ToList();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
Microsoft 文档: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0#customize-webapplicationfactory
class-fixture:https://xunit.net/docs/shared-context#class-fixture