entity-framework - 使用 EF Code First DataContext 进行单元测试

标签 entity-framework unit-testing in-memory-database

这比实际问题更像是一个解决方案/解决方法。我将其发布在这里,因为我在堆栈溢出时或确实在大量谷歌搜索之后找不到此解决方案。

问题:

我有一个使用 EF 4 代码的 MVC 3 webapp,我想为其编写单元测试。我还在编写代码时使用 NCrunch 即时运行单元测试,所以我想避免在此处返回实际数据库。

其他解决方案:

数据上下文

我发现这是创建内存数据上下文的最被接受的方式。它有效地涉及为您的 MyDataContext 编写接口(interface) IMyDataContext,然后在所有 Controller 中使用该接口(interface)。这样做的一个例子是 here .

这是我最初采用的路线,我什至编写了一个 T4 模板来从 MyDataContext 中提取 IMyDataContext,因为我不喜欢维护重复的依赖代码。

但是我很快发现,当使用 IMyDataContext 而不是 MyDataContext 时,一些 Linq 语句在生产中会失败。像这样的具体查询会抛出 NotSupportedException

var siteList = from iSite in MyDataContext.Sites
               let iMaxPageImpression = (from iPage in MyDataContext.Pages where iSite.SiteId == iPage.SiteId select iPage.AvgMonthlyImpressions).Max()
               select new { Site = iSite, MaxImpressions = iMaxPageImpression };

我的解决方案

这其实很简单。我只是为 MyDataContext 创建了一个 MyInMemoryDataContext 子类并覆盖了所有 IDbSet<..> 属性,如下所示:
public class InMemoryDataContext : MyDataContext, IObjectContextAdapter
{
    /// <summary>Whether SaveChanges() was called on the DataContext</summary>
    public bool SaveChangesWasCalled { get; private set; }

    public InMemoryDataContext()
    {
        InitializeDataContextProperties();
        SaveChangesWasCalled = false;
    }

    /// <summary>
    /// Initialize all MyDataContext properties with appropriate container types
    /// </summary>
    private void InitializeDataContextProperties()
    {
        Type myType = GetType().BaseType; // We have to do this since private Property.Set methods are not accessible through GetType()

        // ** Initialize all IDbSet<T> properties with CollectionDbSet<T> instances
        var DbSets = myType.GetProperties().Where(x => x.PropertyType.IsGenericType && x.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>)).ToList();
        foreach (var iDbSetProperty in DbSets)
        {
            var concreteCollectionType = typeof(CollectionDbSet<>).MakeGenericType(iDbSetProperty.PropertyType.GetGenericArguments());
            var collectionInstance = Activator.CreateInstance(concreteCollectionType);
            iDbSetProperty.SetValue(this, collectionInstance,null);
        }
    }

    ObjectContext IObjectContextAdapter.ObjectContext 
    {
        get { return null; }
    }

    public override int SaveChanges()
    {
        SaveChangesWasCalled = true;
        return -1;
    }
}

在这种情况下,我的 CollectionDbSet<> 是 FakeDbSet<> here 的略微修改版本。 (它只是用底层的 ObservableCollection 和 ObservableCollection.AsQueryable() 实现 IDbSet)。

该解决方案适用于我所有的单元测试,特别是与 NCrunch 即时运行这些测试。

完整集成测试

这些单元测试测试所有业务逻辑,但一个主要缺点是您的任何 LINQ 语句都不能保证与您的实际 MyDataContext 一起使用。这是因为针对内存数据上下文进行测试意味着您要替换 Linq-To-Entity 提供程序,而是替换 Linq-To-Objects 提供程序(正如对 this SO 问题的回答中所指出的那样)。

为了解决这个问题,我在单元测试中使用 Ninject 并在单元测试中设置 InMemoryDataContext 来绑定(bind)而不是 MyDataContext。然后,您可以在运行集成测试时使用 Ninject 绑定(bind)到实际的 MyDataContext(通过 app.config 中的设置)。
if(Global.RunIntegrationTest)
    DependencyInjector.Bind<MyDataContext>().To<MyDataContext>().InSingletonScope();
else
    DependencyInjector.Bind<MyDataContext>().To<InMemoryDataContext>().InSingletonScope();

如果您对此有任何反馈,请告诉我,但总会有改进的地方。

最佳答案

根据我在问题中的评论,这更多是为了帮助其他人在 SO 上搜索这个问题。但正如问题下方的评论中指出的那样,还有很多其他设计方法可以解决这个问题。

关于entity-framework - 使用 EF Code First DataContext 进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11032246/

相关文章:

python - Flask-SQLAlchemy 集成测试找不到回滚更改的方法

c# - 如何从 EF 的非异步 SaveChanges 安全地调用异步方法?

android - 如何在 Android 中模拟 Kotlin 的 kotlinx.android.synthetic View

asp.net - 在 Visual Studio Web Developer Express 2010 中测试

javascript - InMemoryWebApi : Angular 2 RC6 + Webpack. 无法解析 InMemoryBackEndService 的所有参数

sql - 为什么 Spark SQL 认为索引的支持不重要?

c# - 将类型传递给泛型类

c# - C# POCO 的 DbGeography 替代方案

entity-framework - 如何防止 Entity Framework 中的 ID 更改漏洞

unit-testing - 使用 PDO Sqlite 内存数据库进行 PHPUnit 测试