2012-06-14 62 views
5

這是一個比實際問題更多的解決方案/解決方法。我在這裏發佈它,因爲我無法在堆棧溢出中找到這個解決方案,或者確實在大量的谷歌搜索之後。使用EF Code First DataContext進行單元測試

問題:

我有第一使用EF 4代碼,我想要寫單元測試用於MVC 3 web應用。我也使用NCrunch在我的代碼中運行單元測試,所以我想避免在這裏支持實際的數據庫。

其他解決方案:

IDataContext

我這個發現的最接受的方式在內存中的DataContext創建。它有效地涉及爲MyDataContext編寫接口IMyDataContext,然後在所有控制器中使用接口。這樣做的一個例子是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 }; 

我的解決方案

這其實很簡單。我只是創造了一個MyInMemoryDataContext子類MyDataContext和壓倒所有如下的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(它只是實現IDbSet與基礎ObservableCollection和ObservableCollection.AsQueryable())。

這個解決方案與我所有的單元測試都很好地結合,特別是與NCrunch一起運行這些測試。

全部集成測試

這些單元測試測試所有的業務邏輯,但一個主要缺點是,沒有你的LINQ語句都保證與實際MyDataContext工作。這是因爲對內存數據上下文進行測試意味着您要替換Linq-To-Entity提供程序,而是使用Linq-To-Objects提供程序(正如在this SO問題的答案中所指出的那樣)。

爲了解決這個問題,我在單元測試中使用了Ninject,並在我的單元測試中設置了InMemoryDataContext來綁定而不是MyDataContext。在運行集成測試時(通過app.config中的設置),您可以使用Ninject綁定到實際的MyDataContext。

if(Global.RunIntegrationTest) 
    DependencyInjector.Bind<MyDataContext>().To<MyDataContext>().InSingletonScope(); 
else 
    DependencyInjector.Bind<MyDataContext>().To<InMemoryDataContext>().InSingletonScope(); 

讓我知道如果您對此有任何反饋,但總是有待改進。

+1

[Here](http://stackoverflow.com/questions/4128640)是一個關於這個的stackoverflow問題,[這裏是一篇文章](http://www.cuttingedge.it/blogs/steven/pivot/ entry.php?id = 84)描述了這個問題的另一種解決方案。 – Steven

+1

[這裏](http://stackoverflow.com/questions/10967921/decouple-ef-queries-from-bl-extension-methods-vs-class-per-query)是關於同一主題的另一個最近的問題。 –

回答

3

根據我對問題的評論,這更多的是幫助其他人在SO上搜索這個問題。但正如在問題的評論中指出的那樣,還有其他一些設計方法可以解決這個問題。