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

22

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


1
除非您告诉我们您要解决的问题,否则很难给出您问题的好答案。您究竟想测试什么? - Craig Stuntz
1
这看起来好像已经在这里得到了回答:http://stackoverflow.com/questions/2065796/objectcontext-never-derives-from-an-interface-how-do-you-apply-di-ioc-in-case-o - Phil Peace
3个回答

21
基本的模拟框架只能为接口和抽象类(但仅限于抽象/虚拟方法)创建模拟。
由于ObjectContext既不是抽象的也不是接口,所以很难对它进行模拟。但是,如果您使用设计器,则生成的具体模型容器将作为部分类,因此您可以将所需的方法/属性从中提取到一个接口中。在您的代码中,您只能使用该接口,然后可以在之后对其进行模拟。
对于ObjectQuery来说,稍微容易一些,因为它有一个基本接口(例如IQueryable),该接口基本上包含您通常需要的所有必要操作(并且需要用于LINQ)。因此,您应该在业务逻辑中公开IQueryable而不是ObjectQuery,并且您可以为该接口创建模拟。
另一种选择是将所有与数据访问相关的逻辑隐藏到单独的层中(具有最小的逻辑),使用集成测试测试该层,并模拟它以能够对其他层进行单元测试。
有一些工具(我只知道TypeMock)使用.NET的分析挂钩来生成模拟。这些工具不限于模拟接口或抽象类,而是可以模拟基本上任何东西,包括非虚拟和静态方法。使用这样的工具,您无需更改业务逻辑即可进行模拟。
尽管这种方法有时很有用,但您必须意识到,将依赖项提取到接口(IoC)中不仅有助于模拟,而且还可以减少组件之间的依赖关系,从而具有其他好处。

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


太棒了,我花了很长时间尝试Mock ObjectQuery,但从未想过在我的Repository中将ObjectQuery替换为IQueryable,当我读到这个时,我真的拍了拍自己的额头... - CodeKiwi
5
确保不要在抽象层面下降到 IEnumerable,否则 LINQ 就会表现不同(查询将在内存中执行而非数据库服务器上)。 - Gaspar Nagy

3
为什么我们不能创建用于测试的实际上下文对象呢?由于我们不希望测试影响生产数据库,因此我们可以始终指定连接字符串指向测试数据库。在运行每个测试之前,构造一个新的上下文,添加您需要在测试中使用的数据,进行单元测试,然后在测试清理部分中删除创建的所有记录。这里唯一的副作用是自动递增的ID将在测试数据库中被使用,但由于它是一个测试数据库 - 谁在乎呢?
我知道大多数关于这个问题的答案都建议使用DI / IoC设计来创建数据上下文等接口。但我使用Entity Framework的原因正是为了不编写任何与我的数据库连接、对象模型和简单的CRUD事务有关的接口。为我的数据对象编写模拟接口并编写复杂的可查询对象以支持LINQ,这违背了依赖于经过高度测试和可靠的Entity Framework的目的。
这种单元测试的模式并不新鲜 - Ruby on Rails 使用它已经很长时间了,而且效果非常好。就像.NET提供EF一样,RoR提供ActiveRecord对象,每个单元测试都创建它所需的对象,进行测试,然后删除所有构建的记录。
如何为测试环境指定连接字符串?由于所有测试都在自己的专用测试项目中,因此添加一个新的App.Config文件以用于测试数据库的连接字符串即可。
只要想想这将为您节省多少头痛和痛苦。

1
在某些情况下,我确信你建议的方法是完全适当的。关于为什么要模拟:1- 在内存中运行数据测试可能会更快。 2- 连接到外部资源会在测试中引入依赖性,使其不太健壮。尽管如此,这些事情可能对你来说都不是问题。 - Christopher Thomas

0

我同意其他人的观点,你实际上无法模拟 ObjectContext。你应该使用 EF DbContext,因为你可以模拟基础的 DbSet。有很多文章介绍如何做到这一点,所以我不会详细说明。然而,如果你绝对必须使用 ObjectContext(由于某些原因),并且你想对其进行单元测试,那么你可以使用 InMemory 数据库。

首先安装这个 Nuget 包:Effort(Entity Framework Fake ObjectContext Realization Tool),它使用 NMemory 作为数据库。安装 Effort.EF6 包:

PM> Install-Package 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);

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接