繁体   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