2013-05-11 20 views
0

我是新來的NHibernate,甚至更新的版本來MOQ(或其他類似的框架)。在日夜搜索(谷歌+ stackoverflow +其他)後,我正在尋求幫助。起訂量NHibernate的ISession.Get不叫模仿會話對象

的情況是(應該是)簡單。我想單元測試一個使用NHibernate作爲ORM層的C#WCF服務的調用。該方法在完成一些初始工作後,找到要連接的數據庫,然後調用SessionProvider(會話工廠的管理器)爲分片數據庫返回nhibernate會話。然後我嘗試使用ISession.Get <>()從數據庫中檢索一個對象,然後做一些工作。問題在於GUID(我在db中查找的條目的關鍵)是在調用開始時生成的,我無法知道它可能在WCF調用的範圍之外。因此,我不能使用sqllite或其他技術來預先填充必要的數據來控制測試。我所希望的是,我可以以某種方式模擬(注入一個虛假的層?)對Session.Get的調用,以返回一個無效的對象,這將導致WCF調用拋出。

這裏的測試代碼片段:

var testRequest = ... (request DTO) 
var dummyBadObject = ... (entity in DB) 

var mock = new Mock<ISession>(MockBehavior.Strict); 
mock.Setup(m => m.Get<SampleObject>(It.IsAny<Guid>())).Returns(dummyBadObject); 

var exception = Assert.Throws<FaultException>(() => applicationService.SomeMethod(testRequest)); 
Assert.AreEqual(exception.Code.ToString(), SystemErrorFault.Code.ToString()); 

當我運行,而不是與模擬的Isession對象交互本次測試,應用服務代碼調用實際的Isession對象的獲取從會話工廠,可連接到數據庫並獲得正確的對象。似乎我錯過了一些非常基本的關於模擬或注入的東西。任何幫助將不勝感激。

感謝, 肖恩

+0

你已經設置了一個模擬的ISession。但你在SessionProvider中註冊的地方在哪裏? – 2013-05-11 22:45:05

+0

SessionProvider是應用程序服務程序集的一部分,它在初始化時初始化靜態會話提供程序,然後創建會話工廠高速緩存以按需提供會話。因此,我無法直接註冊模擬會話,而無需在爲我執行內部工作的應用程序服務上創建測試方法。我希望Moq能夠爲ISession做一個「全局」攔截,然後發出模擬會話。現在,這是我對Moq做什麼的理解完全錯誤的地方。也許微軟的假貨可能是合適的? – Shawn 2013-05-12 02:33:42

回答

1

根據我們的意見,問題是,模擬考試是從你怎麼對他們的看法完全不同。

他們不神奇攔截的從一個接口派生類的作品。它們只是它的動態實現。

創建Mock<ISession>是不是從創建實現ISession一類非常不同。您仍然需要將其注入依賴它的服務中。

您可能會有權審查你的整個堆棧,因爲這樣做的能力取決於一個良好的解耦設計。

提出建議閱讀:Inversion of control

+0

謝謝!看起來我有很好的閱讀能力,這很棒。 – Shawn 2013-05-12 15:12:41

0

我重新設計的部件在我的應用程序有哪些輪流持有ServiceContext對象中的所有其他(曾經被認爲是靜態的)應用程序所使用的組件。在這種情況下,這將是會話提供者(或ISessionFactory緩存),以及類似的WCF通道工廠緩存。不同之處在於,ServiceContext提供了方法來覆蓋不同組件的默認實例,允許我使用模擬替換它們進行測試,並在測試完成時恢復原始組件。這使得我可以建立一個測試,我嘲笑一路從session緩存到ISession.Get /保存/載入等

var mockDatabaseSessionFactory = new Mock<DatabaseSessionManager>(MockBehavior.Strict); 
var mockSession = new Mock<ISession>(MockBehavior.Strict); 
var mockTransaction = new Mock<ITransaction>(MockBehavior.Strict); 

mockDatabaseSessionFactory.Setup(x => x.GetIndividualMapDbSession()).Returns(mockSession.Object); 
mockDatabaseSessionFactory.Setup(x => x.GetIndividualDbSession(It.IsAny<UInt32>())).Returns(mockSession.Object); 
mockDatabaseSessionFactory.Setup(x => x.Dispose()); 
mockSession.Setup(x => x.BeginTransaction()).Returns(mockTransaction.Object); 
mockSession.Setup(x => x.Dispose()); 
mockTransaction.Setup(x => x.Commit()); 
mockTransaction.Setup(x => x.Dispose()); 

// Setups to allow for the map insertion/deletion to pass 
mockSession.Setup(x => x.Get<IndividualMap>(It.IsAny<string>())).Returns((IndividualMap)null); 
mockSession.Setup(x => x.Load<IndividualMap>(It.IsAny<string>())).Returns((IndividualMap)null); 
mockSession.Setup(x => x.Save(It.IsAny<IndividualMap>())).Returns(new object()); 
mockSession.Setup(x => x.Delete(It.IsAny<IndividualMap>())); 

// Our test condition for this test: throw on attempt to save individual 
mockSession.Setup(x => x.Save(It.IsAny<Individual>())) 
    .Throws(new FaultException(ForcedTestFault.Reason, ForcedTestFault.Code)); 

// Test it - but be sure to back up the previous database session factory 
var originalDbSessionFactory = ServiceContext.DatabaseSessionManager; 
ServiceContext.OverrideDatabaseSessionManager(mockDatabaseSessionFactory.Object); 
try 
{ 
    var exception = Assert.Throws<FaultException>(() => applicationService.AddIndividual(addIndividualRequest)); 
    Assert.IsTrue(ForcedTestFault.Code.Name.Equals(exception.Code.Name)); 
} 
catch (Exception) 
{ 
    // Restore the original database session factory before rethrowing 
    ServiceContext.OverrideDatabaseSessionManager(originalDbSessionFactory); 
    throw; 
} 

ServiceContext.OverrideDatabaseSessionManager(originalDbSessionFactory); 
ServiceContext.CommunicationManager.CloseChannel(applicationService); 

幸運的是,代碼設計是不是太糟糕O_O :)所以我重新考慮了這一點,現在代碼覆蓋率達到了100!感謝迭戈讓我朝着正確的方向前進。