【问题标题】:How to mock ObjectContext or ObjectQuery<T> in Entity Framework?如何在实体框架中模拟 ObjectContext 或 ObjectQuery<T>?
【发布时间】:2010-09-29 17:58:31
【问题描述】:

如何在实体框架中模拟 ObjectContext 或 ObjectQuery?

【问题讨论】:

  • 除非您告诉我们您要解决什么问题,否则很难为您的问题提供一个好的答案。你到底想测试什么?
  • 这似乎已经在这里得到了回答:stackoverflow.com/questions/2065796/…

标签: unit-testing entity-framework .net-3.5 mocking


【解决方案1】:

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

由于 ObjectContext 既不是抽象的,也不是接口的,所以模拟它并不容易。但是,由于具体模型容器是作为部分类生成的(如果您使用设计器),您可以从中提取所需的方法/属性到接口。在您的代码中,您只能使用接口,然后可以模拟。

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

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

有一些工具(我只知道TypeMock)使用 .NET 的分析挂钩来生成模拟。这些工具不仅限于模拟接口或抽象类,还可以使用它们模拟任何东西,包括非虚拟和静态方法。使用这样的工具,您无需更改业务逻辑即可进行模拟。

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

我个人喜欢免费软件工具中最好的Rhino.Mocks,但我们也使用TypeMock,这也是一个很棒的产品(但你必须为此付费)。

【讨论】:

  • 太棒了,我花了很长时间尝试模拟 ObjectQuery,但从没想过在我的存储库中将 ObjectQuery 换成 IQueryable,当我读到这篇文章时,我真的拍了一下我的额头......
  • 是的......只要确保你不要去抽象的 IEnumerable 级别,因为这样 LINQ 开始表现不同(查询将在内存中执行,而不是由 db服务器)。
【解决方案2】:

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

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

这种单元测试模式并不新鲜——Ruby on Rails 已经使用了很长时间,而且效果很好。正如 .NET 提供 EF 一样,RoR 提供 ActiveRecord 对象,每个单元测试创​​建它需要的对象,继续测试,然后删除所有构造的记录。

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

试想一下,这将为您节省多少头痛和痛苦。

【讨论】:

  • 在某些情况下,我确信您建议的方法是完全合适的。就您可能要模拟的原因而言: 1 - 对内存中的数据运行测试可能要快得多。 2 - 连接到外部资源会在您的测试中引入依赖性,使其不那么健壮。不过,正如我所说,这些事情对你来说都不是问题。
【解决方案3】:

我同意其他你不能真正模拟 ObjectContext 的观点。您应该使用 EF DbContext 因为您可以模拟底层 DbSet 有很多帖子如何做到这一点。所以我不会写怎么做。但是,如果您绝对必须使用 ObjectContext(出于某种原因)并且想要对其进行单元测试,您可以使用 InMemory 数据库。

首先安装这个Nuget包:Effort(Entity Framework Fake ObjectContext Realization Tool),它使用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);

你在内存数据库中有你的对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-01
    • 1970-01-01
    相关资源
    最近更新 更多