簡體   English   中英

預期對模擬進行一次調用,但使用Func(T,TResult)進行了0次

[英]Expected invocation on the mock once, but was 0 times, with Func(T, TResult)

我似乎對Mock.Verify遇到問題,認為沒有調用方法,但我可以完全驗證它是否存在。

來自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參數除外-我的理解是這不能被嘲笑)。 但是Moq反對這種方法被稱為。

有什么想法我要去哪里嗎?

基本上,問題在於, Verify中的某些內容與運行時的內容不完全匹配(這可能非常善變)。

通過將Runner的代碼更改為:

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

(顯式指定TFileFunctionParameterTFileFunctionOutput

這似乎有助於確定moq驗證匹配的類型。

正如@Lukazoid比我說的要好得多,“ Moq將DoSomething視為與DoSomething不同的方法。”


由於排除了一些候選人:

  • Func<string, ICsvConversionProcessParameter, ProcessOutput>ProcessFile之間似乎不匹配Func<string, ICsvConversionProcessParameter, ProcessOutput>因為ProcessFile似乎沒有定義為func。

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

  • List<ProcessOutput>作為out參數

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);

因此Moq不能真正驗證是否已調用此方法,因為該調用使用了不同的第一類型參數,並且fileFunction Func<,,>也具有另一種類型。 所以這種解釋你的問題。

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可以訪問我的解決方案中的測試。

您可以通過將類設置為public ,或者通過包括以下屬性,使測試程序集成為MoqIssue程序集的“朋友程序集”,來使CsvParam可以訪問:

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("MoqIssueTest")]

在屬於MoqIssue項目的某些文件中。

請注意,Moq框架的internal類型沒有問題,因此您不必為此將Moq的任何程序集轉換為“朋友”。 只需要在MoqIssueTest程序集中輕松地表達“ Verify (即沒有難看的反射)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM