[英]Expected invocation on the mock once, but was 0 times, with Func(T, TResult)
我似乎对Mock.Verify遇到问题,认为没有调用方法,但我可以完全验证它是否存在。
单元测试:
[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);
(显式指定TFileFunctionParameter
和TFileFunctionOutput
)
这似乎有助于确定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);
要调用它,就必须同时指定两个类型参数, TFileFunctionParameter
和TFileFunctionOutput
, 和五个普通参数filePaths
, fileFunction
, fileFunctionParameter
, errorAction
和outputs
。
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);
(类型推断将从中给出正确的TFileFunctionParameter
和TFileFunctionOutput
)。
但是:您已经将测试类放在了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.