在RhinoMocks中,你可以告诉你的模拟IgnoreArguments作为一揽子声明。在Moq中,似乎必须为每个参数指定It.IsAny()。但是,这不适用于ref和out参数。如何测试以下方法,我需要Moq内部服务调用以返回特定结果:
public void MyMethod() {
// DoStuff
IList<SomeObject> errors = new List<SomeObject>();
var result = _service.DoSomething(ref errors, ref param1, param2);
// Do more stuff
}
测试方法:
public void TestOfMyMethod() {
// Setup
var moqService = new Mock<IMyService>();
IList<String> 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<SomeType>()).
Returns(new OtherType()));
}
更新:因此,将错误从“ref”更改为“out”有效。所以看起来真正的问题是有一个你不能注入的ref参数。
答案 0 :(得分:15)
正如您已经发现问题在于您的ref
参数。
Moq目前仅支持ref
参数的精确匹配,这意味着只有在您传递Setup
中使用的相同实例时,该呼叫才会匹配。因此,没有一般匹配,因此It.IsAny()
将无效。
见Moq quickstart
// 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 discussion group:
参考匹配意味着仅在方法匹配时才匹配设置 用同一个实例调用。 It.IsAny很可能返回null 不是你想要的。
在设置中使用与实际呼叫中相同的实例,并且 设置将匹配。
答案 1 :(得分:2)
正如之前提到的@nemesv,It.IsAny返回null,因此您不能将它用作ref参数。为了使调用工作,需要将实际对象传递给它。
如果您无权访问要通过ref传递的对象的创建,则会出现此问题。如果你有权访问真实对象,你可以在测试中使用它,而忘记尝试模拟它。
这是一个使用Extract and Override技术的解决方法,可以让你做到这一点。顾名思义,您将有问题的代码段提取到自己的方法中。然后,重写继承自测试类的测试类中的方法。最后,您设置了您的真实对象,将其传递到新创建的测试类中,并根据需要测试您的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<decimal> items)
{
//This is the problem
var context = new FileContext();
ParseItems(items, ref context);
}
private void ParseItems(IEnumerable<decimal> 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<decimal> 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<decimal> 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<IRecordParser>();
//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<decimal>()
{
10.00m,
11.50m,
12.25m,
14.00m
};
//Act
sut.ParseFile(items);
//Assert
recordParser.Verify(x => x.ParseLine(It.IsAny<decimal>(), ref context), Times.Exactly(items.Count));
}
}
答案 2 :(得分:-1)
回答于:Setting up Moq to ignore a virtual method 我相信在模拟上设置“CallBase = true”会起作用。请参阅快速入门
的“自定义模拟行为”部分