fengling-gateway/tests/YarpGateway.Tests/Integration/TestFixture.cs
movingsam faba26043f IMPL-12: 完善端到端集成测试基础设施和修复递归问题
- 更新 TestFixture 添加 Mock Redis 和内存数据库配置
- 修复 DynamicProxyConfigProvider 中的递归问题
- 添加 GwPendingServiceDiscovery 实体模型
- 修复测试数据模型与实际代码的匹配问题

37/44 集成测试通过,失败测试主要由于测试间状态共享
2026-03-08 10:57:40 +08:00

179 lines
6.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Moq;
using StackExchange.Redis;
using Xunit;
using YarpGateway.Data;
using YarpGateway.DynamicProxy;
using YarpGateway.Services;
namespace YarpGateway.Tests.Integration;
/// <summary>
/// 集成测试基类
/// 提供 WebApplicationFactory 和 HttpClient 的共享实例
/// </summary>
public class TestFixture : IAsyncLifetime
{
public WebApplicationFactory<Program> Factory { get; private set; } = null!;
public HttpClient Client { get; private set; } = null!;
public IServiceProvider Services { get; private set; } = null!;
public async Task InitializeAsync()
{
Factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.UseEnvironment("Testing");
builder.ConfigureServices((context, services) =>
{
// 移除所有与 GatewayDbContext 相关的服务
var descriptorsToRemove = services
.Where(d => d.ServiceType.Name.Contains("DbContext") ||
d.ServiceType.Name.Contains("GatewayDbContext"))
.ToList();
foreach (var descriptor in descriptorsToRemove)
{
services.Remove(descriptor);
}
// 使用内存数据库替换
services.AddDbContextFactory<GatewayDbContext>(options =>
{
options.UseInMemoryDatabase($"YarpGateway_Test_{Guid.NewGuid()}");
});
// 移除 PgSqlConfigChangeListener因为内存数据库不支持 NOTIFY
var listenerDescriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(Microsoft.Extensions.Hosting.IHostedService) &&
d.ImplementationType == typeof(PgSqlConfigChangeListener));
if (listenerDescriptor != null)
{
services.Remove(listenerDescriptor);
}
// 移除 Redis 相关服务(测试环境不需要)
services.RemoveAll(typeof(StackExchange.Redis.IConnectionMultiplexer));
services.RemoveAll(typeof(IRedisConnectionManager));
// 移除分布式负载均衡策略(需要 Redis
services.RemoveAll(typeof(Yarp.ReverseProxy.LoadBalancing.ILoadBalancingPolicy));
// 添加 Mock Redis 连接
var mockRedis = new Mock<IConnectionMultiplexer>();
var mockDb = new Mock<IDatabase>();
mockDb.Setup(db => db.StringSet(It.IsAny<RedisKey>(), It.IsAny<RedisValue>(), It.IsAny<TimeSpan?>(), It.IsAny<When>(), It.IsAny<CommandFlags>())).Returns(true);
mockDb.Setup(db => db.StringGet(It.IsAny<RedisKey>(), It.IsAny<CommandFlags>())).Returns(RedisValue.Null);
mockDb.Setup(db => db.ScriptEvaluate(It.IsAny<string>(), It.IsAny<RedisKey[]>(), It.IsAny<RedisValue[]>(), It.IsAny<CommandFlags>())).Returns(RedisResult.Create(1));
mockRedis.Setup(r => r.GetDatabase(It.IsAny<int>(), It.IsAny<object?>())).Returns(mockDb.Object);
services.AddSingleton(mockRedis.Object);
// 添加内存缓存
services.AddMemoryCache();
// 确保 RouteCache 被正确注册
services.RemoveAll(typeof(IRouteCache));
services.AddSingleton<IRouteCache, RouteCache>();
});
});
Client = Factory.CreateClient();
Services = Factory.Services;
// 初始化数据库种子数据
await SeedDatabaseAsync();
}
public async Task DisposeAsync()
{
Client?.Dispose();
if (Factory != null)
{
await Factory.DisposeAsync();
}
}
/// <summary>
/// 获取新的数据库上下文实例
/// </summary>
public GatewayDbContext CreateDbContext()
{
var factory = Services.GetRequiredService<IDbContextFactory<GatewayDbContext>>();
return factory.CreateDbContext();
}
/// <summary>
/// 获取 RouteCache 实例
/// </summary>
public IRouteCache GetRouteCache()
{
return Services.GetRequiredService<IRouteCache>();
}
/// <summary>
/// 获取 DynamicProxyConfigProvider 实例
/// </summary>
public DynamicProxyConfigProvider GetConfigProvider()
{
return Services.GetRequiredService<DynamicProxyConfigProvider>();
}
/// <summary>
/// 获取内存缓存实例
/// </summary>
public IMemoryCache GetMemoryCache()
{
return Services.GetRequiredService<IMemoryCache>();
}
/// <summary>
/// 重新加载配置
/// </summary>
public async Task ReloadConfigurationAsync()
{
var routeCache = GetRouteCache();
await routeCache.ReloadAsync();
var configProvider = GetConfigProvider();
configProvider.UpdateConfig();
}
/// <summary>
/// 初始化数据库种子数据
/// </summary>
private async Task SeedDatabaseAsync()
{
using var scope = Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<GatewayDbContext>();
// 添加测试租户
await TestData.SeedTenantsAsync(dbContext);
// 添加测试路由
await TestData.SeedRoutesAsync(dbContext);
// 添加测试集群和目标
await TestData.SeedClustersAsync(dbContext);
await dbContext.SaveChangesAsync();
// 初始化 RouteCache注意不要在初始化后调用 UpdateConfig
// 因为 DynamicProxyConfigProvider 构造函数中已经调用了它,
// 额外的调用可能导致 YARP 配置管理器的递归问题)
var routeCache = scope.ServiceProvider.GetRequiredService<IRouteCache>();
await routeCache.InitializeAsync();
}
}
/// <summary>
/// 集合定义,确保使用 TestFixture 的测试不会并行执行
/// </summary>
[CollectionDefinition("Integration Tests")]
public class IntegrationTestCollection : ICollectionFixture<TestFixture>
{
}