如何用内存数据库上下文模拟泛型EntityRepositoryBase<T>中的真实数据库上下文以完成单元测试
如何用内存数据库上下文模拟泛型EntityRepositoryBase中的真实数据库上下文以完成单元测试
我来帮你梳理下当前遇到的问题,以及对应的解决思路:
问题根源
你现在卡在没法给dbRepo.DataContext赋值,本质是因为EntityRepositoryBase<T>里的DataContext属性只有get访问器,没有开放set;而且原基类是通过硬编码的Context.GetContext()获取真实数据库上下文,这种设计本身就很难做单元测试——它把仓库和真实数据库强绑定了。
下面给你两种解决方案,优先推荐第一种(符合软件工程的依赖注入原则):
方案一:重构基类,支持依赖注入(推荐)
这是最规范的做法,通过构造函数注入上下文,让仓库不再硬依赖真实数据库:
1. 修改EntityRepositoryBase<T>的实现
把原来硬编码获取上下文的逻辑改成构造函数注入:
public class EntityRepositoryBase<T> : IDisposable, IEntityRepository<T> where T : class { private readonly Context _dataContext; // 新增构造函数,接收上下文实例 public EntityRepositoryBase(Context dataContext) { _dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext)); } // 保持原有属性,但返回注入的上下文 public Context DataContext { get { return _dataContext; } } public virtual IQueryable<T> GetAll() { return _dataContext.Set<T>().AsQueryable(); } public virtual bool Any(Expression<Func<T, bool>> predicate) { return _dataContext.Set<T>().Any(predicate); } // 完善IDisposable实现,按需释放上下文(如果上下文由仓库管理的话) public void Dispose() { _dataContext?.Dispose(); } }
2. 调整测试上下文的继承关系
让你的MyTestDbContext继承自真实的Context类,这样才能和基类的Context类型匹配:
public class MyTestDbContext : Context // 继承真实业务上下文,而非直接继承DbContext { public MyTestDbContext(DbContextOptions<MyTestDbContext> options) : base(options) { } // 保留或重写需要测试的DbSet public DbSet<PlantType> PlantTypes { get; set; } }
3. 重构待测试的GetPlantData方法所在类
原来的GetPlantData里直接new EntityRepositoryBase<PlantType>(),同样是硬编码,需要改成依赖注入:
public class MasterDataManager { private readonly IEntityRepository<PlantType> _plantTypeRepo; // 构造函数注入仓库依赖 public MasterDataManager(IEntityRepository<PlantType> plantTypeRepo) { _plantTypeRepo = plantTypeRepo ?? throw new ArgumentNullException(nameof(plantTypeRepo)); } public List<DtoTypeEntity> GetPlantData() { // 直接使用注入的仓库实例,不用自己new return _plantTypeRepo.GetAll() .Select(plant => new DtoTypeEntity { // 这里补充你的实体转DTO映射逻辑 PlantId = plant.PlantType_ID, PlantCode = plant.Code, PlantName = plant.Name }) .ToList(); } }
4. 编写测试代码
现在可以轻松用内存上下文注入仓库,完成测试:
public class MasterDataManagerTests { private readonly DbContextOptions<MyTestDbContext> _options; public MasterDataManagerTests() { // 每个测试用唯一的数据库名,避免测试间数据污染 _options = new DbContextOptionsBuilder<MyTestDbContext>() .UseInMemoryDatabase(databaseName: $"TestDb_{Guid.NewGuid()}") .Options; } [Theory] [InlineData("PLANT_TYPE1")] public void MasterDataManager_GetAllMasterData_ReturnPlantList(string screenCode) { // Arrange using var context = new MyTestDbContext(_options); // 添加测试数据 context.PlantTypes.Add(new PlantType { PlantType_ID = 123, Code = "Code1", Name = "ABC", ValidStartDate = DateTime.Now, ValidEndtDate = DateTime.Now }); context.PlantTypes.Add(new PlantType { PlantType_ID = 456, Code = "Code2", Name = "XYZ", ValidStartDate = DateTime.Now, ValidEndtDate = DateTime.Now }); context.SaveChanges(); // 注入测试上下文到仓库 var plantRepo = new EntityRepositoryBase<PlantType>(context); // 注入仓库到待测试类 var manager = new MasterDataManager(plantRepo); // Act var result = manager.GetPlantData(); // Assert Assert.NotNull(result); Assert.Equal(2, result.Count); Assert.Contains(result, dto => dto.PlantCode == "Code1"); Assert.Contains(result, dto => dto.PlantCode == "Code2"); } }
方案二:通过反射修改上下文(不推荐,仅用于无法重构基类的场景)
如果因为历史原因不能修改EntityRepositoryBase<T>的代码,可以用反射强制修改私有字段或属性:
[Theory] [InlineData("PLANT_TYPE1")] public void MasterDataManager_GetAllMasterData_ReturnPlantList(string screenCode) { // Arrange using var context = new MyTestDbContext(_options); // 添加测试数据... context.SaveChanges(); var dbRepo = new EntityRepositoryBase<PlantType>(); // 用反射找到基类里保存上下文的私有字段(假设字段名是_dataContext,你需要根据实际情况调整) var backingField = typeof(EntityRepositoryBase<PlantType>) .GetField("_dataContext", BindingFlags.NonPublic | BindingFlags.Instance); if (backingField != null) { backingField.SetValue(dbRepo, context); } // 后续的Act和Assert逻辑... }
这种方式很脆弱,一旦基类的字段名或结构变化,测试就会失败,所以仅作为临时 workaround 用。
额外注意点
- 使用内存数据库时,一定要给每个测试生成唯一的数据库名(比如用
Guid.NewGuid()),否则不同测试之间会互相污染数据。 - 如果你的
Context类里有配置的实体映射,MyTestDbContext会自动继承这些配置,不用重复写。
备注:内容来源于stack exchange,提问作者Mana G
相关产品推荐
相关产品推荐

