Moq 和 out 参数





5.00/5 (1投票)
如何模拟和测试带有 out 参数的方法
引言
Moq
是一个用于 .NET 的很棒的模拟框架。 它用于单元测试中,将测试类与其依赖项隔离,并确保调用了相关依赖对象的预期方法。
为了完成这项任务,模拟对象的预期调用的方法是通过 Setup
方法的各种重载来设置的。 但是,当方法有 ref/out
参数或为 static
时,正确地模拟方法可能并非易事。 本文重点介绍了如何使用 out
参数模拟方法。 static
方法和 static
属性的情况在另一篇文章中进行了描述。
该解决方案使用 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 的快速入门,可以通过以下代码模拟 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 的快速入门,可以使用具有 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
测试库包含上述测试。
- 所有使用的 IP 地址、服务器名称、工作站、域均为虚构,仅用于演示目的。
- 信息按“原样”提供。