English 中文(简体)
如何在Entity Framework中模拟ObjectContext或ObjectQuery<T>?
原标题:
  • 时间:2009-01-06 04:11:59
  •  标签:

如何在Entity Framework中模拟ObjectContext或ObjectQuery?

问题回答

基本的模拟框架只能为接口和抽象类(但仅限于抽象/虚方法)创建模拟。

由于ObjectContext既不是抽象类也不是接口,因此不容易进行模拟。不过,如果使用设计器,则会生成具体的模型容器作为部分类,您可以将需要的方法/属性从中提取到接口中。在您的代码中,您只需使用该接口,然后可以对其进行模拟。

使用ObjectQuery更容易一些,因为它具有一个基本接口(例如IQueryable),基本包含了通常需要的所有必要操作(以及LINQ所需的操作)。因此,您应该在业务逻辑中公开IQueryable而不是ObjectQuery,并且可以为该接口创建模拟。

另一种选择是将所有数据访问相关的逻辑隐藏到一个单独的层中(带有最小逻辑),使用集成测试来测试这个层,并模拟它以便能够对其他层进行单元测试。

有一些工具(我只知道TypeMock)使用.NET的分析钩子来生成模拟对象。这些工具不仅限于模拟接口或抽象类,而是可以模拟基本上任何东西,包括非虚拟和静态方法。有了这样的工具,您不需要更改业务逻辑以允许模拟。

尽管这种方法有时很有用,但你必须意识到将依赖关系提取到接口(IoC)不仅有助于模拟,而且还可以减少组件之间的依赖关系,从而带来其他好处。

就我个人而言,在免费软件中我最喜欢的是Rhino.Mocks,但我们也使用TypeMock,这也是一个很棒的产品(但需要付费)。

为什么我们不能直接创建用于测试的实际上下文对象?既然我们不想让测试影响生产数据库,我们总是可以指定连接字符串指向测试数据库。在运行每个测试之前,构建一个新的上下文,添加您需要在测试中使用的数据,继续进行单元测试,然后在测试清理部分删除测试期间创建的所有记录。这里的唯一副作用是自动递增的ID将在测试数据库中使用完,但由于它是一个测试数据库,谁在乎呢?

我知道大多数问题的答案提议使用DI / IoC设计来创建数据上下文等接口,但我使用Entity Framework的原因正是为了不编写任何用于我的数据库连接,对象模型和简单的CRUD事务的接口。为我的数据对象编写模拟接口并编写复杂的可查询对象以支持LINQ,这违背了依赖于经过高度测试和可靠的Entity Framework的目的。

这种单元测试的模式并不新鲜 - Ruby on Rails 已经使用了很长时间,并且它非常成功。就像.NET提供EF一样,RoR提供ActiveRecord对象,每个单元测试都创建它需要的对象,进行测试,然后删除所有构建的记录。

如何指定测试环境的连接字符串?由于所有的测试都在它们自己的专用测试项目中,因此添加一个新的 App.Config 文件,其中包含测试数据库的连接字符串就足够了。

只要想想这将为您省去多少头痛和疼痛。

我同意其他人的看法,你不能真正地嘲弄ObjectContext。你应该使用EF DbContext,因为你可以模拟底层的DbSet。有相当多的帖子介绍如何实现这一点。所以我不会写如何做到这一点。然而,如果您绝对必须使用ObjectContext(出于某种原因),并且您想对其进行单元测试,您可以使用InMemory数据库。

首先安装此Nuget包:Effort(Entity Framework伪对象上下文实现工具),该工具使用NMemory作为数据库。安装Effort.EF6包:

PM>安装程序包Effort.EF6

using System;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using Effort;

public class DbContextHelper
{
    //Fake object you can drop this if you are using your own EF context
    private class DbObject
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
    }

    //Fake EF context you can switch with you own EF context
    private class FakeDbContext : DbContext
    {
        public FakeDbContext(DbConnection connection)
            : base(connection, true) { }

        public virtual DbSet<DbObject> DbObjects { get; set; }
    }

    private FakeDbContext _dbContext;

    public DbContextHelper()
    {
        //In memory DB connection
        DbConnection effortConnection = DbConnectionFactory.CreatePersistent("TestInstanceName");
        _dbContext = new FakeDbContext(effortConnection);
    }

    //You can expose your context instead of the DbContext base type
    public DbContext DbContext => _dbContext;

    public ObjectContext ObjectContext => ((IObjectContextAdapter)_dbContext).ObjectContext;

    //Method to add Fake object to the fake EF context
    public void AddEntityWithState(string value, EntityState entityState)
    {
        DbContext.Entry(new DbObject() { Id = Guid.NewGuid(), Name = value }).State = entityState;
    }
}

用途:

DbContextHelper _dbContextHelper = new DbContextHelper();
_dbContextHelper.AddEntityWithState("added", System.Data.Entity.EntityState.Added);
_dbContextHelper.AddEntityWithState("added", System.Data.Entity.EntityState.Modified);

var objs = _dbContextHelper.ObjectContext.GetObjectStateEntries(EntityState.Modified | EntityState.Added);

你已经在内存数据库中拥有你的对象了。





相关问题
热门标签