我似乎遇到了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不同意该方法被称为。
我出错的任何想法?
答案 0 :(得分:1)
基本上,问题是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
之间存在不匹配,因为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);
要调用它,必须指定两个两个类型参数TFileFunctionParameter
和TFileFunctionOutput
,和五个普通参数{{ 1}},filePaths
,fileFunction
,fileFunctionParameter
和errorAction
。
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
类。类型Runner
是CsvParam
到它的程序集。因此,您确实需要在我的解决方案中使internal
可以访问测试。
您可以通过创建类CsvParam
或通过包含属性使测试程序集成为MoqIssue程序集的“友元程序集”来使CsvParam
可访问:
public
属于MoqIssue项目的某个文件。
请注意,Moq框架对using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("MoqIssueTest")]
类型没有任何问题,因此您不必将Moq的任何程序集转换为“朋友”。只需要在MoqIssueTest程序集中轻松表达internal
(即没有难看的反射)。