如何使Moq忽略ref或out的参数

在RhinoMocks中,您可以告诉您的模拟IgnoreArguments作为一揽子声明。 在Moq中,似乎必须为每个参数指定It.IsAny()。 但是,这不适用于ref和out参数。 如何测试以下方法我需要Moq内部服务调用以返回特定结果:

public void MyMethod() { // DoStuff IList errors = new List(); var result = _service.DoSomething(ref errors, ref param1, param2); // Do more stuff } 

测试方法:

 public void TestOfMyMethod() { // Setup var moqService = new Mock(); IList errors; var model = new MyModel(); // This returns null, presumably becuase "errors" // here does not refer to the same object as "errors" in MyMethod moqService.Setup(t => t.DoSomething(ref errors, ref model, It.IsAny()). Returns(new OtherType())); } 

更新:因此,将错误从“ref”更改为“out”有效。 所以看起来真正的问题是有一个你无法注入的ref参数。

正如您已经发现问题在于您的ref参数。

Moq目前仅支持ref参数的精确匹配,这意味着只有在您传递Setup使用的相同实例时,调用才匹配。 所以没有一般​​的匹配,所以It.IsAny()不起作用。

请参阅Moq 快速入门

 // ref arguments var instance = new Bar(); // Only matches if the ref argument to the invocation is the same instance mock.Setup(foo => foo.Submit(ref instance)).Returns(true); 

和Moq 讨论组 :

参考匹配意味着仅当使用相同实例调用方法时才匹配设置。 It.IsAny返回null,所以可能不是你想要的。

在设置中使用与实际调用中相同的实例,设置将匹配。

正如@nemesv之前提到的,It.IsAny返回null,因此您不能将它用作ref参数。 为了使调用工作,需要将实际对象传递给它。

当您无权访问要通过ref传递的对象的创建时,会发生此问题。 如果你有权访问真实对象,你可以在测试中使用它,而忘记尝试模拟它。

这是一个使用Extract and Override技术的解决方法,它允许您这样做。 顾名思义,您可以将有问题的代码段提取到自己的方法中。 然后,重写inheritance自测试类的测试类中的方法。 最后,设置真实对象,将其传递到新创建的测试类中,并根据需要测试ref参考调用。

这里有一些(人为的)代码,但它显示了之前和之后的结果,最后通过了测试。

 using System; using System.Collections.Generic; using Moq; using MoqRefProblem; using NUnit.Framework; namespace MoqRefProblem { //This class is the one we want to have passed by ref. public class FileContext { public int LinesProcessed { get; set; } public decimal AmountProcessed { get; set; } } public interface IRecordParser { //The ref parameter below is what's creating the testing problem. void ParseLine(decimal amount, ref FileContext context); } //This is problematic because we don't have a //seam that allows us to set the FileContext. public class OriginalFileParser { private readonly IRecordParser _recordParser; public OriginalFileParser(IRecordParser recordParser) { _recordParser = recordParser; } public void ParseFile(IEnumerable items) { //This is the problem var context = new FileContext(); ParseItems(items, ref context); } private void ParseItems(IEnumerable items, ref FileContext context) { foreach (var item in items) { _recordParser.ParseLine(item, ref context); } } } } //This class has had the creation of the FileContext extracted into a virtual //method. public class FileParser { private readonly IRecordParser _recordParser; public FileParser(IRecordParser recordParser) { _recordParser = recordParser; } public void ParseFile(IEnumerable items) { //Instead of newing up a context, we'll get it from a virtual method //that we'll override in a test class. var context = GetFileContext(); ParseItems(items, ref context); } //This is our extensibility point protected virtual FileContext GetFileContext() { var context = new FileContext(); return context; } private void ParseItems(IEnumerable items, ref FileContext context) { foreach (var item in items) { _recordParser.ParseLine(item, ref context); } } } //Create a test class that inherits from the Class under Test //We will set the FileContext object to the value we want to //use. Then we override the GetContext call in the base class //to return the fileContext object we just set up. public class MakeTestableParser : FileParser { public MakeTestableParser(IRecordParser recordParser) : base(recordParser) { } private FileContext _context; public void SetFileContext(FileContext context) { _context = context; } protected override FileContext GetFileContext() { if (_context == null) { throw new Exception("You must set the context before it can be used."); } return _context; } } [TestFixture] public class WorkingFileParserTest { [Test] public void ThisWillWork() { //Arrange var recordParser = new Mock(); //Note that we are an instance of the TestableParser and not the original one. var sut = new MakeTestableParser(recordParser.Object); var context = new FileContext(); sut.SetFileContext(context); var items = new List() { 10.00m, 11.50m, 12.25m, 14.00m }; //Act sut.ParseFile(items); //Assert recordParser.Verify(x => x.ParseLine(It.IsAny(), ref context), Times.Exactly(items.Count)); } } 

回答于: 设置Moq以忽略虚拟方法我相信在模拟上设置“CallBase = true”将起作用。 请参阅快速入门的“自定义模拟行为”部分