2013-09-29 118 views
1

我正在嘗試使用.net mvc 4和流利的nhibernate來創建應用程序。如何爲使用nHibernate的方法編寫單元測試

我創建了ProductsFacade,它負責獲取數據並將數據插入數據庫。 方法GetProductsByPageAndCategory用於從數據庫中獲取記錄頁面。我想編寫單元測試,檢查分頁是否工作正常。

這很難做,因爲分頁必須在單個QueryOver查詢中完成。我不能編寫單獨的方法來獲取數據,模擬它併爲分頁編寫單獨的方法。所以我需要模擬數據庫。我使用moq工具來嘲笑。

也許任何人都可以提供一些關於如何做到這一點的提示?或者其他的選擇如何解決我的問題?

public class ProductFacade { 
    //... 

    public virtual IList<Product> GetProductsByPageAndCategory(
     string category, 
     int pageNumber, 
     int pageSize) 
    { 
     //IList<Product> products = ; 
     var productsQuery = _session.QueryOver<Product>(); 
     if (category != null) 
     { 
      productsQuery.Where(p => p.Category == category); 
     } 

     IList<Product> products = productsQuery 
      .OrderBy(p => p.Id) 
      .Desc 
      .Skip((pageNumber - 1) * pageSize) 
      .Take(pageSize) 
      .List<Product>(); 

     return products; 
    } 

    //... 
} 
+0

是的,嘲笑'session'變量是解決方案。然後,您可以驗證對後續流暢方法的調用是否正確完成。有什麼問題? – BartoszKP

回答

2

我也用起訂量爲嘲諷NHibernate的會議上,這裏是如何嘲笑NHibernate的ISessionISessionFactory一個很簡單的例子。

var mockSession = new Mock<ISession>(); 
    mockSession.Setup(r => r.Get<ExampleEntity>(It.IsAny<int>())) 
     .Returns(new ExampleEntity()); 

    var mockSessionFactory = new Mock<ISessionFactory>(); 
    mockSessionFactory.Setup(r => r.OpenSession()) 
     .Returns(mockSession.Object); 

    var sessionFactory = mockSessionFactory.Object; 
    // inject mockSessionFactory.Object to your business layer... 

    // code where ever sessionFactory is used... 
    // OpenSession now returns the mocked session 
    using (var session = sessionFactory.OpenSession()) 
    { 
     //Get of type ExampleEntity will always return whatever you define in your mock 
     var rs = session.Get<ExampleEntity>(1); 
    } 

要使用嘲笑你的業務對象,你就必須在某種程度上,你可以手動構造它,以便它使用您的嘲笑工廠設計它。

通常這很容易,如果你使用Unity注入例如。有了統一,你的商務類的構造函數可能需要會話或工廠或其他...在這種情況下,在你的單元測試中,你可以手動構建目標並將其傳遞給它...

+0

我使用Moq嘲笑會話,並使用公開'IQueryable '的IDbService,這樣我就可以控制包含NHibernate會話的單元測試控制器,甚至在您的案例中使用您的外觀。我剛剛從@OdeToCode那裏得到了這個想法。但是有些人不喜歡這種做事的方式 – Rippo

1

這是我的替代方案 - 不要模擬數據庫。

在我們的測試設置中,在每個開發人員的機器上必須有一個具有給定名稱的數據庫(例如「CMS_AutoTests」)。當測試運行時,它與這個數據庫進行交互。

TearDown方法在每個測試運行後運行一個存儲過程來清除每個表,因此每個測試都從一個空數據庫開始。

+1

我們幾乎做了同樣的事情,我認爲這些測試更好,因爲它們更接近集成測試。 –

+0

@ColeW這實際上是集成測試......但不是單元測試。單元測試不應該關心數據,你只需測試小單元(一段代碼),而不想從數據庫中讀取/寫入任何數據。所以對於propper單元測試,你必須模擬你的數據庫。 對於集成測試,當然,你將不得不爲每個測試集建立一個測試數據庫... – MichaC

+1

@Ela我同意你的說法,但是如果你正在進行集成測試,那麼誰需要單元測試呢?讓我們面對它,無論如何你都無法繞過集成測試,所以爲什麼寫2個測試基本上做同樣的事情。我開始採用相同的方式,最終沒有嘲笑數據庫。 –

2

內存數據庫比嘲笑少得多的代碼,更容易理解和接近真實的東西。它還確保您的映射是正確的,因此不需要額外的負載保存測試。

//for all tests 

static Configuration config; 
static ISessionFactory testSessionFactory; 

config = Fluently.Configure() 
    .Database(SQLiteConfiguration.Standard.InMemory().ShowSql().FormatSql()) 
    .Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<Foo>()) // add your mappings here 
    .BuildConfiguration(); 

testSessionFactory = config.BuildSessionFactory(); 

// in test 
public ProductTests() 
{ 
    session = sf.OpenSession(); 
    new SchemaExport(config).Execute(true, true, false, session.Connection, null); 
} 

private void InjectSampleData(params object[] objects) 
{ 
    foreach (var obj in objects) 
    { 
     session.Save(obj); 
    } 
    session.Flush(); 
    session.Clear(); 
} 

public void TestThis() 
{ 
    InjectSampleData(
     new Product() { ... }, 
     new Product() { ... }, 
     new Product() { ... }, 
     new Product() { ... }, 
    ); 

    var products = new ProductFacade(session).GetProductsByPageAndCategory(...); 

    // assert products match expected subcollection of Injected Data 
} 
相關問題