目录
Moq是.Net的绝佳模拟框架。它在单元测试中用于将测试的类与其依赖项隔离开,并确保要调用依赖对象的预期方法。
为了执行此任务,应通过各种Setup方法重载来设置要调用的模拟对象的那些方法。但是,当方法具有ref/out参数或为静态方法时,正确的模拟方法可能并非易事。本文关注如何使用out参数模拟方法。
该解决方案使用Moq的快速入门中介绍的功能。
解决方案使用C#7,.Net 4.6.1和NuGet包Moq,FluentAssertions和xUnit。
让我们考虑一下带有out参数方法的服务:
public interface IService
{
void ProcessValue(string inputValue, out string outputValue);
}
以及使用此服务的简单类:
public class Class1
{
private readonly IService _service;
public Class1(IService service)
{
_service = service;
}
/// <summary>
/// Return the trimmed value of input string processed by service.
/// </summary>
/// <param name="inputValue">Input value, could be null.</param>
/// <param name="outputValue">Output value.</param>
public string ProcessValue(string inputValue)
{
_service.ProcessValue(inputValue, out string outputValue);
return outputValue;
}
}
现在我们要编写一些覆盖ProcessValue方法的测试。
根据Moq的Quickstart,可以用以下代码模拟 out参数:
// out arguments
var outString = "ack";
// TryParse will return true, and the out argument will return "ack", lazy evaluated
mock.Setup(foo => foo.TryParse("ping", out outString)).Returns(true);
在第一个测试中使用此方法:
[Theory]
[InlineData(null)]
[InlineData("short")]
[InlineData("\t\t\ttabbed\t\t")]
[InlineData("long long value")]
public void Class1_Return_ConstValueWithoutCallback(string inputValue)
{
// arrange
var expectedValue = "Expected value";
var service = new Mock<IService>();
service
.Setup(mock => mock.ProcessValue(It.IsAny<string>(), out expectedValue))
.Verifiable();
// act
var class1 = new Class1(service.Object);
var actualValue = class1.ProcessValue(inputValue);
// assert
actualValue.Should().NotBeNull();
actualValue.Should().Be(expectedValue);
service.Verify();
}
在这个例子中,out参数具有不依赖于输入值的预定义值。此外,如果被测试的例程期望out参数的值取决于输入的测试参数,则可以通过以下方式修改此测试:
[Theory]
[InlineData(null)]
[InlineData("short")]
[InlineData("\t\t\ttabbed\t\t")]
[InlineData("long long value")]
public void Class1_Return_TheSameValueWithoutCallback(string inputValue)
{
// arrange
var expectedValue = $"Output {inputValue}";
var service = new Mock<IService>();
service
.Setup(mock => mock.ProcessValue(It.IsAny<string>(), out expectedValue))
.Verifiable();
// the same code
// ...
}
有时它很有用,但是如果您想获得传递的参数的实际值,例如inputValue或设置out参数的值,则应使用Callback方法。
根据Moq的Quickstart,可以对具有ref/out参数的方法进行回调:
// callbacks for methods with `ref` / `out` parameters are possible
// but require some work (and Moq 4.8 or later):
delegate void SubmitCallback(ref Bar bar);
mock.Setup(foo => foo.Submit(ref It.Ref<Bar>.IsAny))
.Callback(new SubmitCallback((ref Bar bar) => Console.WriteLine("Submitting a Bar!")));
因此,让我们用IService的ProcessValue方法(使用复制-粘贴方法)来定义具有相同签名的委托:
private delegate void ServiceProcessValue(string inputValue, out string outputValue);
然后可以使用Callback方法定义IService模拟:
[Theory]
[InlineData(null)]
[InlineData("short")]
[InlineData("\t\t\ttabbed\t\t")]
[InlineData("long long value")]
public void Class1_Return_NewValueWithCallback(string inputValue)
{
// arrange
string actualInputValue = null;
const string outputValue = "Inner value";
var expectedValue = "Not used value";
var service = new Mock<IService>();
service
.Setup(mock => mock.ProcessValue(It.IsAny<string>(), out expectedValue))
.Callback(new ServiceProcessValue(
(string input, out string output) =>
{
actualInputValue = input;
output = outputValue;
}))
.Verifiable();
// act
var class1 = new Class1(service.Object);
var actualValue = class1.ProcessValue(inputValue);
// assert
actualValue.Should().NotBeNull();
actualValue.Should().Be(outputValue);
actualInputValue.Should().Be(inputValue);
service.Verify();
}
Callback使用委托创建返回值,传递的值可以保存,如第19行所示。此外,在第20行,out参数的值设置为outputValue,并且不使用Setup声明中设置的值。它在第32行声明。
提供的解决方案具有ConsoleApp控制台应用程序和ConsoleApp.Tests测试库。
ConsoleApp包含具有Service实现类的IService接口,并且Class1消费服务。在main方法中,ProcessValue方法运行于多个值。
ConsoleApp.Tests 测试库包含上述测试。