2014-01-10 26 views
2

的方法的調用我想使用Moq或RhinoMocks來模擬此接口,以驗證是否將正確的表達式作爲參數傳遞(並且願意切換到任何其他開放源代碼可以支持本嘲笑庫):嘲弄和驗證包含表達式<Func<T,bool>>參數

完整的源代碼:

public class Record 
{ 
    public int RecordId { get; set; } 
} 

public interface IRepository 
{ 
    void DeleteRecordsByFilter(Expression<Func<Record, bool>> filter); 
} 

public class MyClass 
{ 
    private readonly IRepository _repo; 

    public MyClass(IRepository repo) 
    { 
     _repo = repo; 
    } 

    public void DeleteRecords(int recordId) 
    { 
     _repo.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId)); 
    } 
} 

[TestFixture] 
public class MyFixture 
{ 
    [Test] 
    public void DeleteRecordsShouldCallDeleteRecordsByFilterOnRepo() 
    { 
     const int recordId = 10; 

     var repo = new Mock<IRepository>(); 
     repo.Setup(method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId))); 

     var sut = new MyClass(repo.Object); 
     sut.DeleteRecords(recordId); 

     repo.Verify(method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(recordId))); 
    } 
} 

當我執行單元測試失敗,錯誤:

Moq.MockException : Expected invocation on the mock at least once, but was never performed: method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(10))

Configured setups: method => method.DeleteRecordsByFilter(x => x.RecordId.Equals(10)), Times.Never

Performed invocations: IRepository.DeleteRecordsByFilter(x => x.RecordId.Equals(value(MyClass+<>c__DisplayClass0).recordId))

+0

發佈你的完整代碼... –

+0

DeleteRecords正在返回一些東西,但它的返回值是'void'? –

+0

@ChrisMantle道歉,現在修復。 –

回答

1

如果您使用起訂量,和AR不願意真正檢查,正確的參數傳遞給你的功能,我想這this is what you are looking for:

_mock.Setup(method => method.DeleteRecordsByFilter(It.IsAny<Expression<Func<Record,bool>>>()); 

(這是equilvalent到.IgnoreArguments()使用RhinoMock)

問題是,即使兩個表達式是「相同」(邏輯上相同),它們不是對象的相同引用,並且System.Linq.Expression不會實現.Equals()覆蓋。因此,他們對於Moq是「不同的」。

這就是爲什麼你會得到你的例外。

如果你想比較你的表達式,你將不得不在你的模擬上設置一個回調來比較兩個表達式(例如使用ExprssionVisitor)。

[Test] 
public void DeleteRecordsShouldCallDeleteRecordsByFilterOnRepo() 
{ 
    const int recordId = 10; 

    var repo = new Mock<IRepository>(); 
    Expression<Func<Record,bool>> exp = x => x.RecordId.Equals(recordId); 
    repo.Setup(method => method.DeleteRecordsByFilter(exp)); 

    var sut = new MyClass(repo.Object); 
    sut.DeleteRecords(recordId); 

    repo.Verify(method => method.DeleteRecordsByFilter(exp)); 
} 

但其測試什麼:)

+0

不錯的嘗試:)我需要驗證傳遞到存儲庫的表達式是否正確。 –

+0

然後創建類似於'Expression > filter = x => x.RecordId.Equals(10);'。用相同的'filter'對象進行設置和驗證。這可能會工作。 –

1

this postExpression比較:

編輯

的一個例子,如果實施這樣的測試應該工作可能非常脆弱,因爲默認是通過參考,並且需要尋找替代品。然而,使用所引用的方法,即通過表達Body的簡單ToString()比較,可以驗證表達像這樣:

var _mock = new Mock<IInterfaceToBeMocked>(); 

// "Act" step -> invoke your CUT, which in turn calls the mocked interface dependency 
// (Obviously your CUT will do this, but just to prove the point ...) 
_mock.Object.DeleteRecordsByFilter(x => x.RecordId.Equals(10)); 

// Back to the unit test 
Expression<Func<Record, bool>> goodComparison = x => x.RecordId.Equals(10); 
Expression<Func<Record, bool>> badComparison = x => x.RecordId > 10 && x.RecordId < 12; 
_mock.Verify(m => m.DeleteRecordsByFilter(
    It.Is<Expression<Func<Record, bool>>>(ex => ex.Body.ToString() == goodComparison.Body.ToString())), 
    Times.Once); 
_mock.Verify(m => m.DeleteRecordsByFilter(
    It.Is<Expression<Func<Record, bool>>>(ex => ex.Body.ToString() == badComparison.Body.ToString())), 
    Times.Never()); 

(即Moq保留Expression類型的調用參數的列表,就像任何其它類型的參數可以在一個或VerifySetup使用)

編輯的

上述工作對於其中沒有外部或CLOS瑣碎情況表達式中的變量,因此兩個表達式都等價地序列化。並且,在CUT將相同的表達式實例傳遞給依賴關係的情況下,來自@Oliver的引用相等將起作用。但在一般情況下,您需要確定2個表達式是否等價,例如用一組已知的輸入和輸出調用這兩個表達式(但這更多的是與表達式/函數/ Lambdas等價的問題 - 而不是真正的Moq問題)。

+1

不會工作,因爲Body.ToString()不會返回相同的東西,如果您傳遞變量(它會返回值(MyClass + <> c__DisplayClass0).recordId而不是10) – Olivier

+0

我的意思是,在一個「真實」測試用例中: ) 在你嚴格的例子中,它顯然會起作用,因爲你可以訪問傳遞給函數的實際表達式實例。 – Olivier

+1

不,他們是2個不同的表達式實例(除非編譯器實現它們FWR)。但你對這個常數的觀點是很好的。在表達式中使用任何類型的變量都會導致不同的表達式序列化。如果CUT在內部構建表達式,那麼無法控制發送給模擬的精確表達式引用。 – StuartLC

相關問題