65.9K
CodeProject 正在变化。 阅读更多。
Home

Moq 和 out 参数

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2020 年 11 月 19 日

CPOL

2分钟阅读

viewsIcon

48559

如何模拟和测试带有 out 参数的方法

引言

Moq 是一个用于 .NET 的很棒的模拟框架。 它用于单元测试中,将测试类与其依赖项隔离,并确保调用了相关依赖对象的预期方法。

为了完成这项任务,模拟对象的预期调用的方法是通过 Setup 方法的各种重载来设置的。 但是,当方法有 ref/out 参数或为 static 时,正确地模拟方法可能并非易事。 本文重点介绍了如何使用 out 参数模拟方法。 static 方法和 static 属性的情况在另一篇文章中进行了描述。

该解决方案使用 Moq 的快速入门中描述的功能。

背景

该解决方案使用 C#7、.NET 4.6.1 和 NuGet 包 MoqFluentAssertionsxUnit

问题

让我们考虑一下具有 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 包含具有实现类 ServiceIService 接口,以及使用服务的 Class1。 在 main 方法中,ProcessValue 方法在多个值上运行。

ConsoleApp.Tests 测试库包含上述测试。


  1. 所有使用的 IP 地址、服务器名称、工作站、域均为虚构,仅用于演示目的。
  2. 信息按“原样”提供。
© . All rights reserved.