如何在具有存储库模式的实体框架中伪造 DbContext.Entry 方法
Posted
技术标签:
【中文标题】如何在具有存储库模式的实体框架中伪造 DbContext.Entry 方法【英文标题】:How to fake DbContext.Entry method in Entity Framework with repository pattern 【发布时间】:2013-12-26 15:20:36 【问题描述】:因为我想对我的代码进行单元测试,所以我在我的 MVC4 应用程序中实现了存储库模式。我设法通过遵循this 代码制作了一个上下文接口、一个假上下文并使用了一个System.Data.Entity.DbSet
的假实现。
不幸的是,就像我面前的两张海报(here 和 here)一样,我无法模拟 DbContext.Entry method
。我使用此方法更新代码中的数据库条目,如下所示:
DbContext.Entry(order).State = EntityState.Modified;
我还没有找到解决这个问题的方法,只有这样说的人:
“那么对这段代码进行单元测试有什么意义?你伪造了 Find 方法,然后你伪造 DbEntityEntry 并且将没有真正的逻辑 测试。”
或到
在继续之前阅读this 和所有相关问题。 (...) 如果您想测试您的存储库,请创建与真实数据库对话的集成测试。
这一切都很好,但问题仍然没有答案。我阅读了批评,我仍然想要这个 Entry 方法,所以我将能够在我的单元测试中使用假上下文并使用模拟对象。当然,我也会使用集成测试,但它们不如一些快速单元测试快。
我在尝试一些实现时收到的错误是Error 2 'Project.Models.Order' does not contain a definition for 'State' and no extension method 'State' accepting a first argument of type '[whatever return type I use]' could be found (are you missing a using directive or an assembly reference?)
希望有人能帮我制作一个假的 DbContext.Entry 方法。
【问题讨论】:
使用存储库模式,其中存储库实现与工作单元模式耦合的通用接口。这样,您只需要模拟或伪造工作单元即可。 我查看了您链接到的两个 SO 帖子中的第一个,但有一个答案被忽略了。但这可能与您的问题完全无关。请发布您的代码,以便我提供一个好的答案。 @KeithPayne 有答案吗? 我找到了this answer。 我正在使用存储库模式,但我想测试我的存储库! 【参考方案1】:通过“添加额外的间接级别”我们得到答案here:
public virtual void SetModified(object entity)
Entry(entity).State = EntityState.Modified;
并在我们的控制器中使用DbContext.SetModified(entity)
。
mockContext.Setup(c => c.SetModified(It.IsAny()));
【讨论】:
【参考方案2】:为了解决这个问题,我添加了一个方法重载,并添加了一个过时的属性来查看调用原始方法的位置。
public virtual void Entry<TEntity>(TEntity entity, Action<DbEntityEntry<TEntity>> action) where TEntity : class
action(base.Entry(entity));
[Obsolete("Use overload for unit tests.")]
public new DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class
return base.Entry(entity);
/** or **/
throw new ApplicationException("Use overload for unit tests.");
那你可以DbContext.Entry(order, ent => ent.State = EntityState.Modified;
【讨论】:
【参考方案3】:如何实现基于接口的存储库和工作单元以获得您所追求的示例:
public interface IRepository<T>
T FindSingle(Expression<Func<T, Boolean>> predicate, params Expression<Func<T, object>>[] includeExpressions);
void ProxyGenerationOn();
void ProxyGenerationOff();
void Detach(T entity);
void Add(T newEntity);
void Modify(T entity);
void Attach(T entity);
void Remove(T entity);
void SetCurrentValues(T modifiedEntity, T origEntity);
T GetById(int id);
T GetById(int id, bool sealOverride);
IQueryable<T> GetAll();
IQueryable<T> GetAll(bool sealOverride);
IQueryable<T> GetAll(string[] EagerLoadPaths);
IQueryable<T> Find(Expression<Func<T, Boolean>> predicate);
public interface IUnitOfWork : IDisposable
//repository implementations go here
bool SaveChanges()
注意上下文是如何被完全抽象出来的。您只需要在具体的实现中担心它。
【讨论】:
详细说明:OP 应该在他的Modify()
实现中调用DbContext.Entry(entity).State = EntityState.Modified;
。然后在使用存储库测试代码时,您只需模拟存储库并验证repository.Modify()
是否被调用。
@CodeCaster 这是一个很好的解释。
@Maess 我对此很陌生,工作单元只是抽象出来的方法调用吗?例如,我不直接使用DbContext.SaveChanges
,而是使用repository.SaveChanges
,并且上下文在我实例化的存储库中?
您会在您的工作单元上调用 SaveChanges,而后者又会在您的上下文中调用 SaveChanges。这种模式通常与 IOC 的一些 for 一起使用。【参考方案4】:
您可以使用DbContext.Update(entity)
。就我而言,它运行良好,因为我嘲笑了DbContext
。
_dbContext.Update(entity);
【讨论】:
这更像是一个评论而不是一个答案 根据标签,这不是 [entity-framework-core] 问题,EF5 中似乎不存在该方法。以上是关于如何在具有存储库模式的实体框架中伪造 DbContext.Entry 方法的主要内容,如果未能解决你的问题,请参考以下文章