如何在EF Core中模拟EntityEntry以测试并发异常处理逻辑?
我正尝试为Entity Framework Core中一个处理异常并重载实体的方法编写单元测试,该方法代码如下:
catch (DbUpdateConcurrencyException) { await context.Entry(myEntityInstance).ReloadAsync(); } catch { return null; };
已成功模拟SaveChangesAsync抛出预期异常,但在尝试模拟Entry方法时遇到问题:
contextMock.Setup(c => c.Entry(It.IsAny<MyEntity>()).ReloadAsync(default))
这会引发ArgumentException:
Can not instantiate proxy of class: Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry ...
Could not find a parameterless constructor. (Parameter 'constructorArguments') ---> System.MissingMethodException: Constructor on type 'Castle.Proxies.EntityEntry`1Proxy' not found.
问题在于EntityEntry<TEntity>不便于模拟,因为它仅有一个构造函数:
[EntityFrameworkInternal] public EntityEntry(InternalEntityEntry internalEntry)
InternalEntityEntry是EF Core的内部类,依赖其他内部类实例。EF团队暂无修改计划,请问如何在EF Core中有效模拟EntityEntry以测试该方法?
可行解决方案
1. 采用内存数据库进行集成测试
放弃模拟DbContext,直接使用EF Core的InMemory数据库创建真实的DbContext实例。这种方式能完全复用EF Core的真实逻辑,避免模拟内部类的麻烦:
- 创建配置为内存数据库的DbContext实例
- 向数据库添加目标实体并保存
- 修改实体状态以触发
DbUpdateConcurrencyException - 调用待测试方法,验证实体是否被正确重载
这种方法更贴近真实业务场景,测试结果可信度更高,是优先推荐的方案。
2. 封装Entity操作到自定义接口
将Entry和ReloadAsync的逻辑封装到独立接口中,让业务类依赖该接口而非直接依赖DbContext:
public interface IEntityReloader { Task ReloadEntityAsync<T>(DbContext context, T entity) where T : class; } public class EntityReloader : IEntityReloader { public async Task ReloadEntityAsync<T>(DbContext context, T entity) where T : class { await context.Entry(entity).ReloadAsync(); } }
在业务类中注入IEntityReloader,测试时只需模拟这个接口的ReloadEntityAsync方法,无需直接处理EntityEntry的模拟问题:
var reloaderMock = new Mock<IEntityReloader>(); reloaderMock.Setup(r => r.ReloadEntityAsync(It.IsAny<DbContext>(), It.IsAny<MyEntity>())) .Returns(Task.CompletedTask); // 将reloaderMock.Object注入到待测试类中执行测试
3. 反射构造EntityEntry(不推荐)
通过反射创建InternalEntityEntry实例并构造EntityEntry,但这种方法依赖EF Core的内部实现,后续版本更新可能导致代码失效:
- 启用
InternalsVisibleTo允许测试项目访问EF Core的内部类 - 反射获取
InternalEntityEntry的构造函数,构造所需的内部依赖实例 - 创建
EntityEntry实例并通过Moq的Returns方法返回
由于需要处理大量内部依赖,实现复杂且维护成本高,仅在无法使用前两种方案时考虑。
内容的提问来源于stack exchange,提问作者IvanH

