Unit testing 如何模拟ObjectContext或ObjectQuery<;T>;在实体框架中?

Unit testing 如何模拟ObjectContext或ObjectQuery<;T>;在实体框架中?,unit-testing,entity-framework,.net-3.5,mocking,Unit Testing,Entity Framework,.net 3.5,Mocking,如何在实体框架中模拟ObjectContext或ObjectQuery?基本模拟框架只能为接口和抽象类创建模拟(但只能为抽象/虚拟方法创建模拟) 由于ObjectContext既不是抽象的,也不是接口,因此模拟它并不容易。但是,由于具体模型容器是作为分部类生成的(如果使用设计器),因此可以将所需的方法/属性从中提取到接口。在您的代码中,您可以只使用接口,以后可以对其进行模拟 使用ObjectQuery更容易一点,因为它有一个基本接口(例如IQueryable),基本上包含您通常需要(以及LINQ

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

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

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

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

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

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

尽管这种方法有时很有用,但您必须知道,提取接口依赖项(IoC)不仅有助于模拟,而且还可以减少组件之间的依赖项,这还有其他好处


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

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

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

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

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


想想看,这能帮你减轻多少头痛和疼痛

我同意其他人的看法,你不可能真正地模仿上下文。您应该使用EF DbContext,因为您可以模拟底层的DbSet。有很多文章介绍了如何做到这一点。所以我不会写怎么做。然而,如果您绝对必须使用ObjectContext(出于某种原因),并且希望对其进行单元测试,则可以使用InMemory数据库

首先安装这个Nuget包:Effort(实体框架Fake ObjectContext实现工具),它使用NMemory作为数据库。安装efforce.EF6软件包:

PM>安装包efforce.EF6


这就是内存数据库中的对象。

除非您告诉我们您要解决的问题,否则很难给您一个好的答案。你到底想测试什么?这里似乎已经给出了答案:太棒了,我花了这么长时间试图模拟ObjectQuery,但从来没有想过在我的存储库中将ObjectQuery替换为IQueryable,当我读到这篇文章时,我真的打了我的额头……是的。。。只需确保在抽象中不要降低IEnumerable级别,因为这样LINQ就开始表现出不同的行为(查询将在内存中执行,而不是由db服务器执行)。在某些情况下,我确信您建议的方法是完全合适的。关于为什么要模拟:1-对内存中的数据运行测试可能要快得多。2-连接到外部资源会在测试中引入依赖性,从而降低测试的健壮性。正如我所说,这些事情对你来说都不是问题。
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);