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

如何用内存数据库上下文模拟泛型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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 08:04:29