如何在具有存储库模式的实体框架中伪造 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 =&gt; 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 方法的主要内容,如果未能解决你的问题,请参考以下文章

存储库模式和聚合根模式和实体框架

具有实体框架 4.1 和父/子关系的存储库模式

使用实体框架、代码优先和 CRUD 操作的存储库模式

具有存储库模式的实体框架,将数据插入到具有多对多关系的表中

具有实体框架的CRUD WEB API

DAL 中的实体框架存储库模式,如何实现更新功能?