当前位置: 首页 > 工具软件 > moq > 使用案例 >

Moq和out参数

荆亦
2023-12-01

目录

源代码

介绍

背景

问题

没有回调的模拟

模拟回调

解决方案


源代码

介绍

Moq.Net的绝佳模拟框架。它在单元测试中用于将测试的类与其依赖项隔离开,并确保要调用依赖对象的预期方法。

为了执行此任务,应通过各种Setup方法重载来设置要调用的模拟对象的那些方法。但是,当方法具有ref/out参数或为静态方法时,正确的模拟方法可能并非易事。本文关注如何使用out参数模拟方法。

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

背景

解决方案使用C7.Net 4.6.1NuGetMoqFluentAssertionsxUnit

问题

让我们考虑一下带有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方法的测试。

没有回调的模拟

根据MoqQuickstart,可以用以下代码模拟 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方法。

模拟回调

根据MoqQuickstart,可以对具有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!")));

因此,让我们用IServiceProcessValue方法(使用复制-粘贴方法)来定义具有相同签名的委托:

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 测试库包含上述测试。

 

 类似资料: