You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Web Application Factory中替换Cosmos数据库用于集成测试?

解决Cosmos DB生产与测试环境数据库获取逻辑分离的方案

核心思路

抽象数据库获取逻辑,通过依赖注入在生产和测试环境提供不同实现:

  • 生产环境:直接调用GetDatabase,严格遵循最佳实践,不自动创建数据库
  • 测试环境:创建唯一临时数据库(用CreateDatabaseIfNotExists),测试完成后自动销毁

具体实现步骤

1. 定义抽象接口

创建IDatabaseProvider接口,封装数据库获取和清理逻辑:

public interface IDatabaseProvider
{
    Task<Database> GetDatabaseAsync(CancellationToken cancellationToken = default);
    Task CleanupDatabaseAsync(CancellationToken cancellationToken = default);
}

2. 生产环境实现

实现生产用的数据库提供者,仅获取已存在的数据库:

public class ProductionDatabaseProvider : IDatabaseProvider
{
    private readonly CosmosClient _cosmosClient;
    private readonly string _databaseName;

    public ProductionDatabaseProvider(CosmosClient cosmosClient, IConfiguration configuration)
    {
        _cosmosClient = cosmosClient;
        _databaseName = configuration["Cosmos:DatabaseName"] ?? "Cocktails";
    }

    public Task<Database> GetDatabaseAsync(CancellationToken cancellationToken = default)
    {
        // 生产环境直接获取,不执行创建操作
        return Task.FromResult(_cosmosClient.GetDatabase(_databaseName));
    }

    public Task CleanupDatabaseAsync(CancellationToken cancellationToken = default)
    {
        // 生产环境无需清理
        return Task.CompletedTask;
    }
}

3. 测试环境实现

创建测试用的提供者,生成唯一临时数据库,测试后销毁:

public class TestDatabaseProvider : IDatabaseProvider
{
    private readonly CosmosClient _cosmosClient;
    private readonly string _testDatabaseName;
    private Database? _database;

    public TestDatabaseProvider(CosmosClient cosmosClient)
    {
        _cosmosClient = cosmosClient;
        // 生成唯一数据库名,避免并行测试冲突
        _testDatabaseName = $"Cocktails_Test_{Guid.NewGuid():N}";
    }

    public async Task<Database> GetDatabaseAsync(CancellationToken cancellationToken = default)
    {
        if (_database == null)
        {
            // 测试环境自动创建数据库(如果不存在)
            _database = await _cosmosClient.CreateDatabaseIfNotExistsAsync(
                id: _testDatabaseName,
                cancellationToken: cancellationToken);
        }
        return _database;
    }

    public async Task CleanupDatabaseAsync(CancellationToken cancellationToken = default)
    {
        if (_database != null)
        {
            // 测试完成后删除临时数据库
            await _database.DeleteAsync(cancellationToken: cancellationToken);
        }
    }
}

4. 注册服务

在生产代码中注册CosmosClient和生产用的数据库提供者:

// 注册CosmosClient单例(保留你原有的代码)
builder.Services.AddSingleton<CosmosClient>(serviceProvider =>
{
    var client = new CosmosClient(
        accountEndpoint: Environment.GetEnvironmentVariable("Cosmos:Uri"),
        tokenCredential: new DefaultAzureCredential()
    );

    return client;
});

// 注册生产环境数据库提供者
builder.Services.AddSingleton<IDatabaseProvider, ProductionDatabaseProvider>();

5. 业务代码中使用

在服务类中注入IDatabaseProvider,替代直接调用CosmosClient获取数据库:

public class CocktailService
{
    private readonly IDatabaseProvider _databaseProvider;

    public CocktailService(IDatabaseProvider databaseProvider)
    {
        _databaseProvider = databaseProvider;
    }

    public async Task CreateCocktailAsync(CocktailModel cocktail, CancellationToken cancellationToken)
    {
        var database = await _databaseProvider.GetDatabaseAsync(cancellationToken);
        var container = database.GetContainer("Recipes");
        await container.CreateItemAsync(cocktail, cancellationToken: cancellationToken);
    }
}

6. 测试中替换服务并清理

使用WebApplicationFactory替换服务,并通过IAsyncLifetime在测试前后处理数据库清理:

public class CocktailApiTests : IAsyncLifetime
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly IDatabaseProvider _testDatabaseProvider;

    public CocktailApiTests()
    {
        _factory = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(services =>
                {
                    // 移除生产环境的数据库提供者
                    var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IDatabaseProvider));
                    if (descriptor != null)
                    {
                        services.Remove(descriptor);
                    }

                    // 注册测试用的数据库提供者
                    services.AddSingleton<IDatabaseProvider, TestDatabaseProvider>();
                });
            });

        // 获取测试数据库提供者实例,用于后续清理
        _testDatabaseProvider = _factory.Services.GetRequiredService<IDatabaseProvider>();
    }

    [Fact]
    public async Task CreateCocktail_ReturnsSuccessStatusCode()
    {
        var client = _factory.CreateClient();
        var testCocktail = new CocktailResponse { Name = "Test Margarita", Instructions = "Mix tequila and lime" };

        var response = await client.PostAsJsonAsync("/cocktails", testCocktail);
        response.EnsureSuccessStatusCode();

        // 可添加更多断言,比如验证数据是否存入数据库
    }

    // 测试完成后清理临时数据库
    public async Task DisposeAsync()
    {
        await _testDatabaseProvider.CleanupDatabaseAsync();
        await _factory.DisposeAsync();
    }

    public Task InitializeAsync() => Task.CompletedTask;
}

方案优势

  • 符合最佳实践:生产环境严格使用GetDatabase,避免意外创建数据库;测试环境用临时库,隔离测试数据
  • 低侵入性:业务代码无需修改,仅通过依赖注入切换实现
  • 并行测试安全:每个测试类生成唯一数据库名,避免测试间数据干扰
  • 自动清理:测试完成后自动销毁临时数据库,不会留下冗余数据

内容的提问来源于stack exchange,提问作者Jakob Busk Sørensen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:09:57