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

.NET EF Core泛型仓储单元测试:DbContext.Entry模拟失败求助

解决EntityEntry无法Mock的问题

问题根源在于EntityEntry<T>没有公共无参构造函数,Moq无法直接生成其代理对象。以下是几种可行的解决方案:

方案1:抽象DbContext为接口(推荐,解耦且测试稳定)

通过定义抽象接口隔离DbContext依赖,避免直接Mock EF Core的内部类型:

  1. 定义业务所需的DbContext接口
public interface IAppDbContext
{
    IEntityEntry<T> Entry<T>(T entity) where T : class;
    void Attach<T>(T entity) where T : class;
    void Remove<T>(T entity) where T : class;
}
  1. 让实际DbContext实现该接口
public class AppDbContext : DbContext, IAppDbContext
{
    // 原有DbContext配置(如DbSet、OnConfiguring等)

    public IEntityEntry<T> Entry<T>(T entity) where T : class
    {
        return base.Entry(entity);
    }

    public void Attach<T>(T entity) where T : class
    {
        base.Attach(entity);
    }

    public void Remove<T>(T entity) where T : class
    {
        base.Remove(entity);
    }
}
  1. 修改BaseRepository依赖抽象接口
public class BaseRepository<TEntity> where TEntity : class
{
    protected readonly IAppDbContext _dbContext;

    public BaseRepository(IAppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public virtual void Delete(TEntity entity)
    {
        if (_dbContext.Entry(entity).State == EntityState.Detached)
        {
            _dbContext.Attach(entity);
        }
        _dbContext.Remove(entity);
    }
}
  1. 编写单元测试
[Fact]
public void Delete_Entity_CallsDbContextRemoveOnce()
{
    // Arrange
    var testCustomer = new Customer { Name = "Test-Customer" };
    
    var entityEntryMock = new Mock<IEntityEntry<Customer>>();
    entityEntryMock.Setup(e => e.State).Returns(EntityState.Unchanged);
    
    var dbContextMock = new Mock<IAppDbContext>();
    dbContextMock.Setup(d => d.Entry(testCustomer)).Returns(entityEntryMock.Object);
    
    var repository = new CustomerRepository(dbContextMock.Object);
    
    // Act
    repository.Delete(testCustomer);
    
    // Assert
    dbContextMock.Verify(x => x.Remove(testCustomer), Times.Once);
    // 可选:验证Attach未被调用(因为实体状态不是Detached)
    dbContextMock.Verify(x => x.Attach(testCustomer), Times.Never);
}

方案2:使用EF Core InMemory数据库(集成测试,更贴近真实场景)

如果不想做接口抽象,可以用InMemory数据库替代Mock,直接验证真实的EF操作逻辑:

[Fact]
public void Delete_Entity_RemovesItFromDatabase()
{
    // Arrange
    var options = new DbContextOptionsBuilder<AppDbContext>()
        .UseInMemoryDatabase(databaseName: "Test_Delete_Integration")
        .Options;

    var testCustomer = new Customer { Id = 1, Name = "Test-Customer" };
    // 先将实体添加到数据库
    using (var context = new AppDbContext(options))
    {
        context.Customers.Add(testCustomer);
        context.SaveChanges();
    }

    // Act
    using (var context = new AppDbContext(options))
    {
        var repository = new CustomerRepository(context);
        repository.Delete(testCustomer);
        context.SaveChanges();
    }

    // Assert
    using (var context = new AppDbContext(options))
    {
        var deletedCustomer = context.Customers.Find(1);
        Assert.Null(deletedCustomer);
    }
}

方案3:反射创建EntityEntry实例(不推荐,依赖EF内部实现)

如果必须直接Mock DbContext,可以通过反射绕过无参构造函数限制,但该方法依赖EF Core内部实现,版本更新后可能失效:

[Fact]
public void Delete_Entity_CallsRemove()
{
    var testCustomer = new Customer { Name = "Test-Customer" };
    
    var dbContextMock = new Mock<DbContext>();
    // 反射创建EntityEntry并设置状态
    var entityEntry = CreateEntityEntry(testCustomer, EntityState.Unchanged);
    dbContextMock.Setup(d => d.Entry(testCustomer)).Returns(entityEntry);
    
    var repository = new CustomerRepository(dbContextMock.Object);
    repository.Delete(testCustomer);
    
    dbContextMock.Verify(x => x.Remove(testCustomer), Times.Once);
}

private EntityEntry<T> CreateEntityEntry<T>(T entity, EntityState state) where T : class
{
    // 获取EntityEntry的内部构造函数
    var constructor = typeof(EntityEntry<T>).GetConstructor(
        BindingFlags.Instance | BindingFlags.NonPublic,
        new[] { typeof(DbContext), typeof(T) });
    
    var entry = (EntityEntry<T>)constructor.Invoke(new object[] { new DbContext(), entity });
    
    // 反射设置实体状态
    var stateProperty = typeof(EntityEntry<T>).GetProperty(nameof(EntityEntry<T>.State));
    stateProperty.SetValue(entry, state);
    
    return entry;
}

内容的提问来源于stack exchange,提问作者Themis Zarotiadis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 10:39:59