模拟一次的预期调用,但是0次,与Func(T,TResult)

时间:2016-01-22 13:55:46

标签: c# unit-testing moq

我似乎遇到了Mock.Verify的问题,认为某个方法没有被调用,但我可以完全验证它是不是。

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参数除外 - 我的理解是这不能被模拟)。然而,Moq不同意该方法被称为。

我出错的任何想法?

2 个答案:

答案 0 :(得分:1)

基本上,问题是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之间存在不匹配,因为ProcessFile似乎没有被定义为功能。

  • 我可以看到的另一个潜在差异是string[] vs IEnumerable<string>

  • List<ProcessOutput>作为out param

答案 1 :(得分: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五个普通参数{{ 1}},filePathsfileFunctionfileFunctionParametererrorAction

C#非常有用并且提供了类型推断,我们不必在源代码中编写类型参数。编译器会计算出我们想要的类型参数。但两种类型的论点仍然存在,只是“看不见”。要查看它们,请将鼠标悬停在下面的泛型方法调用上(Visual Studio IDE将显示它们),或者查看输出IL。

所以在你的outputs课程中,这个电话的确意味着:

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

所以Moq无法真正验证这是否被调用,因为调用使用不同的第一类型参数,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); fileFunction也有另一种类型。所以这种解释了你的问题。

NikolaiDante展示了如何更改Func<,,>以实际使用runner期望的类型参数。

但是更改 test 并保持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); TFileFunctionParameter

但是:您已将测试类放在另一个项目/程序集中,而不是TFileFunctionOutput类。类型RunnerCsvParam到它的程序集。因此,您确实需要在我的解决方案中使internal可以访问测试。

您可以通过创建类CsvParam或通过包含属性使测试程序集成为MoqIssue程序集的“友元程序集”来使CsvParam可访问:

public

属于MoqIssue项目的某个文件。

请注意,Moq框架对using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("MoqIssueTest")] 类型没有任何问题,因此您不必将Moq的任何程序集转换为“朋友”。只需要在MoqIssueTest程序集中轻松表达internal(即没有难看的反射)。