2012-05-09 54 views
3

我很難理解這裏是否有正確的方法。我想測試一個存儲庫。存儲庫依賴於DbContext。我希望能夠驗證存儲庫沒有調用一個函數添加一個屬於DbContext成員的IDbSet屬性。如何將SingleOrDefault與Rhino嘲諷存檔

我試過兩種方法。驗證行爲,並用狀態進行驗證。似乎驗證行爲是正確的,因爲誰知道虛假對象中存在狀態。

public void VerifyBehaviour() 
    { 
     // Arrange 
     var stubEntities = MockRepository.GenerateStub<IWsStatContext>(); 
     var stubManufcturers = MockRepository.GenerateStub<IDbSet<Manufacturer>>(); 
     var manufacturer = new Manufacturer() { Name = "Dummy" }; 
     var manufacturers = new List<Manufacturer>(); 
     manufacturers.Add(manufacturer); 

     stubManufcturers.Stub(x => x.Local).Return(new System.Collections.ObjectModel.ObservableCollection<Manufacturer>(manufacturers)); 
     stubManufcturers.Stub(x => x.SingleOrDefault(m => m.Name == "Dummy")).Return(manufacturer); 
     stubEntities.Manufacturers = stubManufcturers; 

     // Act 
     var sut = new EquiptmentRepository(stubEntities); 
     sut.AddManufacturer(manufacturer); 

     // Assert 
     stubManufcturers.AssertWasNotCalled(x => x.Add(manufacturer)); 
    } 


    public void VerifyState() 
    { 
     // Arrange 
     var stubEntities = MockRepository.GenerateStub<IWsStatContext>(); 
     var stubManufacturers = new InMemoryDbSet<Manufacturer>(); 
     var manufacturer = new Manufacturer() { Name = "Dummy" }; 
     stubManufacturers.Add(manufacturer); 
     stubEntities.Manufacturers = stubManufacturers; 

     // Act 
     var sut = new EquiptmentRepository(stubEntities); 
     sut.AddManufacturer(manufacturer); 

     // Assert 
     Assert.AreEqual(stubManufacturers.Count(), 1); 
    } 

驗證行爲方法失敗,在SingleOrDefault的存根周圍出現NullReferenceExceptions。所以我找到最好的帖子來驗證狀態並使用假DbSet。但是檢查假物體的狀態感覺不對。如果Add函數的實現方式與真實的不同(它最初是和我的測試即使我的存儲庫被破壞傳遞的)也會如何。

有誰知道如何存根SingleOrDefault,所以我可以檢查添加被稱爲?我無法檢查添加在非rhinomocked存根上調用。

由於

回答

1

SingleOrDefaultIEnumerable<T>(其IDbSet<T>器具)定義的擴展方法。作爲擴展方法意味着它是一個靜態方法。 RhinoMocks(或任何其他免費工具)cannot mock/stub static methods

可惜的是你沒有很多選擇在這裏:你要麼必須做基於狀態的驗證,或創建手工製作的模擬和手動設置它爲您的測試(但是這很可能將結束基於狀態驗證再次 - 你不能真的存根SingleOrDefault)。

編輯提取物和覆蓋例如:

首先,你需要提取你的類稍後將覆蓋不同的方法問題的一部分。這個問題的部分是自然與IDbSet互動:

public class EquiptmentRepository 
{ 
    public void Add(Manufacturer m) 
    { 
     // perform some local logic before calling IDbSet.Add 
     this.AddToDbSet(m); 
    } 

    protected virtual AddToDbSet(Manufacturer m) 
    { 
     this.context.Manfuacturers.Add(m); 
    } 
} 

現在,在您的EquiptmentRepository可測試版本,覆蓋AddToDbSet通過簡單地增加一些狀態驗證,以使測試更加簡單,例如:

internal void TestableEquiptmentRepository: EquiptmentRepository 
{ 
    internal List<Manufacturer> AddedManufacturers = new List<Manufacturer>(); 

    protected override void AddToDbSet(Manufacturer m) 
    { 
     // we're no longer calling DbSet.Add but kind of rolling 
     // our own basic mock and tracking what objects were 
     // add by simply adding them to internal list 
     this.AddedManufacturers.Add(m); 
    } 
} 

每當Add被叫,通過製造商將被添加到列表,如果它將被添加到DbSet在實際情況。在您的測試,你可以簡單地驗證類狀態的可測試版本:

[Test] 
public void AddManufacturer_DoesNotAddExistingManufacturersToDbSet() 
{ 
    // Arrange 
    var stubEntities = MockRepository.GenerateStub<IWsStatContext>(); 
    var stubManufacturers = MockRepository.GenerateStub<IDbSet<Manufacturer>>(); 
    var manufacturer = new Manufacturer() { Name = "Dummy" }; 
    stubManufacturers.Add(manufacturer); 
    stubEntities.Manufacturers = stubManufacturers; 

    // Act 
    var sut = new TestableEquiptmentRepository(stubEntities); 
    sut.AddManufacturer(manufacturer); 

    // Assert 
    Assert.AreEqual(sut.AddedManufacturers.Count(), 0); 
} 

這樣你可以驗證EquiptmentRepository.Add所有的邏輯,而不需要用DbSet在所有互動。

+0

謝謝你。所以一般來說,如果你不能使用存根嘲諷框架,然後退回到狀態驗證? – user1385713

+0

@ user1385713:基本上,是的。有一種叫做* extract和override *的技術,你可以將你的類的有問題的部分提取到'virtual'方法,並且使你的* sut派生類覆蓋這些虛擬方法(例如提供假值/存根)。這很難看,但可以在某些情況下工作。使用可靠的對象來驗證狀態(就像你在你的'VerifyState'中做的那樣)是沒問題的。 –

0

你不能用RhinoMocks或Moq(TypeMock can)來模擬靜態方法。

虛假對象的狀態驗證實際上驗證您的模擬,而不是您的系統在測試。

有一個technique它可以讓你的代碼可測試(但我認爲價格太高)。你將不得不提取擴展方法的接口,並將其替換System.Linq.Enumerable擴展使用自己:

var item = items.MyExtensions().SingleOrDefault(); 

BTW,當我用靜態方法的嘲諷的面孔,我平時做的以下幾件事之一:

  • 傳遞靜態方法執行的結果。例如如果我需要測試方法中的當前日期,而不是調用DateTime.Today,則將當前時間作爲參數Foo(DateTime date)傳遞。
  • 將靜態調用包裝在某些非靜態對象中。例如如果我需要獲取一些配置設置,而不是調用ConfigurationManager.AppSettings["foo"],我創建了非靜態類BarConfiguration,它將所有工作委託給靜態ConfigurationManager(並實現接口IBar { int Foo { get; })。
  • 不要嘲笑它。看起來我正在測試一些我不該測試的東西(枚舉擴展,日誌等)

請考慮不要單元測試您的存儲庫。數據訪問邏輯的集成測試更有意義。

3

jimmy_keen's answer指出:

SingleOrDefaultIEnumerable<T>定義的擴展方法(IDbSet<T>器具)。作爲擴展方法意味着它是一種靜態方法。 RhinoMocks(或任何其他免費工具)不能模擬/存根靜態方法。

而不是試圖「存根」的擴展方法,嘗試磕碰的擴展方法是基於建立在底層接口:IEnumerable<T>

stubManufcturers.Stub(x => x.GetEnumerator()).Return(new List<Manufacturer> { manufacturer }.GetEnumerator()); 

通過測試框架的GetEnumerator()行爲時SingleOrDefault被稱爲會按預期執行僞枚舉,測試將能夠評估行爲。