2016-01-22 49 views
1

我似乎遇到了Mock.Verify的問題,它認爲沒有調用某個方法,但我可以完全驗證它是否正確。預期的模擬調用一次,但是0次,使用Func(T,TResult)

Runnable version from Git

單元測試:

替代單元測試
[Test] 
public void IterateFiles_Called() 
{ 
    Mock<IFileService> mock = new Mock<IFileService>(); 
    var flex = new Runner(mock.Object); 

    List<ProcessOutput> outputs; 
    mock.Verify(x => x.IterateFiles(It.IsAny<IEnumerable<string>>(), 
        It.IsAny<Func<string, ICsvConversionProcessParameter, ProcessOutput>>(), 
        It.IsAny<ICsvConversionProcessParameter>(), 
        It.IsAny<FileIterationErrorAction>(), 
        out outputs), Times.Once); 

     } 

(後下面的評論)

[Test] 
public void IterateFiles_Called() 
{ 
    Mock<IFileService> mock = new Mock<IFileService>(); 
    var flex = new Runner(mock.Object); 

    List<ProcessOutput> outputs; 
    mock.Verify(x => x.IterateFiles(It.IsAny<string[]>(), 
         flex.ProcessFile, //Still fails 
         It.IsAny<ICsvConversionProcessParameter>(), 
         It.IsAny<FileIterationErrorAction>(), 
         out outputs), Times.Once); 

} 

Runner.cs:

public class Runner 
    { 
     public Runner(IFileService service) 
     { 
      string[] paths = new[] {"path1"}; 

      List<ProcessOutput> output = new List<ProcessOutput>(); 

      service.IterateFiles(paths, ProcessFile, new CsvParam(), FileIterationErrorAction.ContinueThenThrow, out output); 
     } 

     public ProcessOutput ProcessFile(string file, ICsvConversionProcessParameter parameters) 
     { 
      return new ProcessOutput(); 
     } 
    } 

當我調試我可以看到service.IterateFiles被調用。另外,由於所有參數都用It.IsAny<T>標記,所以傳遞的參數無關緊要(out參數除外 - 我的理解是這不能被嘲弄)。但莫克不同意這種方法。

任何想法我錯了嗎?

+0

@NikolaiDante - 現在的Git https://github.com/medmondson/Moq-Func-Issue可用 –

+0

您完全掩蓋一個事實,可運行版本,你的方法'無效IterateFiles (IEnumerable的文件路徑, Func fileFunction, TFileFunctionParameter fileFunctionParameter, FileIterationErrorAction errorAction, out outputs);'是泛型的,需要兩個類型參數'TFileFunctionParameter'和'TFileFunctionOutput'。當你在'Runner'中調用方法時,你應該知道'TFileFunctionParameter'和'TFileFunctionOutput'的替換類型(推理)。 –

+0

...(續)然後,您需要以重現這些推斷類型的方式編寫您的'It.IsAny <>()'表達式(在'Verify'內)。要查找'Runner'內使用的'TFileFunctionParameter'和'TFileFunctionOutput'的值,只需將鼠標懸停在Visual Studio中的方法調用上並讀取類型即可。如果難以閱讀,請檢查生成的IL。你使用什麼版本的C#編譯器和.NET運行時(CLR)? –

回答

1

NikolaiDante的回答與它下面的評論意見,本質上給出瞭解釋。儘管如此,由於我已經調查了一下,所以我會盡力寫清楚。

您的問題完全無法顯示您的問題的主要原因,即該方法是通用之一。我們必須去看你鏈接的Git文件,以瞭解這一點。

IFileService聲明的方法是:

void IterateFiles<TFileFunctionParameter, TFileFunctionOutput>(
    IEnumerable<string> filePaths, 
    Func<string, TFileFunctionParameter, TFileFunctionOutput> fileFunction, 
    TFileFunctionParameter fileFunctionParameter, 
    FileIterationErrorAction errorAction, 
    out List<TFileFunctionOutput> outputs); 

稱呼它,一個必須指定兩個類型參數,TFileFunctionParameterTFileFunctionOutput五個普通參數filePathsfileFunctionfileFunctionParametererrorActionoutputs

C#是有幫助的,並提供類型推斷與我們不必在源代碼中寫入類型參數。編譯器會計算出我們想要的類型參數。但這兩種類型的論點仍然存在,只有「隱形」。要查看它們,請將鼠標懸停在下面的通用方法調用上(並且Visual Studio IDE會顯示它們),或者查看輸出IL。

所以你Runner類中,調用真正含義是:

service.IterateFiles<CsvParam, ProcessOutput>(paths, 
    (Func<string, CsvParam, ProcessOutput>)ProcessFile, 
    new CsvParam(), FileIterationErrorAction.ContinueThenThrow, out output); 

注重兩個兩種類型的第一行,並注意方法組ProcessFile實際上變成了Func<string, CsvParam, ProcessOutput>即使方法簽名看起來更像Func<string, ICsvConversionProcessParameter, ProcessOutput>。代表可以通過類似的方法組創建。 (它是不是真正相關的是Func<in T1, in T2, out TResult>T2標記爲逆變。)

如果我們檢查您Verify,那麼我們看到的類型推斷真的把它看作:

mock.Verify(x => x.IterateFiles<ICsvConversionProcessParameter, ProcessOutput>(
    It.IsAny<IEnumerable<string>>(), 
    It.IsAny<Func<string, ICsvConversionProcessParameter, ProcessOutput>>(), 
    It.IsAny<ICsvConversionProcessParameter>(), 
    It.IsAny<FileIterationErrorAction>(), 
    out outputs), Times.Once); 

所以起訂量不能真正驗證這稱爲,因爲調用使用不同的第一類型參數,並且fileFunctionFunc<,,>有另一種類型。所以這種解釋你的問題。

NikolaiDante顯示瞭如何更改runner以實際使用Verify預期的類型參數。

但感覺更合適兩個更改測試並保持runner代碼不變。因此,我們要在測試中,是:

mock.Verify(x => x.IterateFiles(It.IsAny<IEnumerable<string>>(), 
    It.IsAny<Func<string, CsvParam, ProcessOutput>>(), 
    It.IsAny<CsvParam>(), 
    It.IsAny<FileIterationErrorAction>(), 
    out outputs), Times.Once); 

(類型推斷會給從這個正確的TFileFunctionParameterTFileFunctionOutput)。

但是:您已將測試類放在另一個項目/程序集中,而不是Runner類。並且CsvParam的類型爲internal。所以你真的需要使CsvParam可以在我的解決方案中進行測試。

您可以CsvParam訪問或者通過使類public,或由包括屬性使得測試組​​件MoqIssue組件的「朋友集結號」:

using System.Runtime.CompilerServices; 

[assembly: InternalsVisibleTo("MoqIssueTest")] 

在某些文件屬於MoqIssue項目。

請注意,Moq框架與internal類型沒有任何問題,因此您不必將Moq的任何裝配變成「朋友」。只需在您的MoqIssueTest組件中輕鬆地(即沒有難看的反射)表達Verify即可。

+0

並看到[模擬泛型方法調用任何給定的類型參數](http://stackoverflow.com/questions/5311023/)一個模糊的相關線程,它顯然沒有「it-is-any」for類型參數。 –

+0

哇 - 優秀的答案!非常感謝你。我現在把這個標記爲答案,因爲它更全面地回答了我的問題。 –

1

基本上,問題是Verify中的某些內容與運行時的內容不完全相符(可能會變得相當浮躁)。

我能得到它通過改變代碼Runner傳遞到:

service.IterateFiles<ICsvConversionProcessParameter, ProcessOutput>(paths, ProcessFile, new CsvParam(), FileIterationErrorAction.ContinueThenThrow, out output); 

(Specifiying TFileFunctionParameterTFileFunctionOutput明確)

這似乎有助於指甲挫的類型起訂量的核實,以配合。

由於@Lukazoid說的比我好得多,「Moq把DoSomething當成DoSomething的一種不同方法。」


有些考生,因爲排除:

  • 似乎有Func<string, ICsvConversionProcessParameter, ProcessOutput>ProcessFile作爲ProcessFile之間的不匹配似乎並沒有被定義爲FUNC。

  • 我可以看到的另一個潛在差異是string[] vs IEnumerable<string>

  • List<ProcessOutput>作爲出PARAM

+0

「進程文件」可分配給' Func '因此我相信Is.IsAny

+0

我不確定賦值是否足以讓它匹配。如果您暫時公開並更改驗證,測試是否通過? – NikolaiDante

+0

我已經公開(無濟於事),你如何建議更改驗證? –

相關問題