Moq and out parameter

Source code

Introduction

Moq is a great mocking framework for .Net. It is used in unit testing to isolate the tested class from its dependencies and to make sure that the expected methods of the dependent objects are being called.

To carry out this task, those methods of the mocked objects which are expected to be called are set by various overloads of the Setup method. But to mock the method correctly could be not easy task when the method has ref/out parameters or is static. The post pays attention at how to mock the method with out parameter. The case of the static methods and static properties is described in the another post.

The solution uses features that are described in Moq’s Quickstart.

Original post is written for Svitla blog.

Background

Solution uses C#7, .Net 4.6.1, and NuGet packages Moq, FluentAssertions and xUnit.

Problem

Let’s consider the service that has the method with out parameter:

public interface IService
{
	void ProcessValue(string inputValue, out string outputValue);
}

and the simple class that uses this service:

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;
	}
}

Now we would like to write several tests that cover ProcessValue method.

Mock without callback

According to Moq’s Quickstart, out parameter could be mocked by the following code:

// 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);

This approach is used in the first test:

[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();
}

In this case out parameter has the predefined value that does not depend on input values. Moreover, if the tested routine expects that the value of the out parameter depends on input test parameters, this test could be modified in the following way:

[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
	// ...
}

Sometimes it is useful, but if you’d like to get actual values of the passed arguments like inputValue or to set the value of the out parameter, Callback method should be used.

Mock with callback

According to Moq’s Quickstart, callbacks for methods with ref / out parameters are possible:

// 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!")));

So let’s define the delegate with the same signature as ProcessValue method from IService (using copy-paste approach):

private delegate void ServiceProcessValue(string inputValue, out string outputValue);

Then IService mock could be defined with Callback method:

[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 uses a delegate to create the return value and the passed values could be saved as is shown at the line 19. Moreover, at the line 20 the value of the out parameter is set to outputValue and the value that is set in the Setup declaration is not used. It is asserted at the line 32.

Solution

Provided solution has ConsoleApp console application and ConsoleApp.Tests test library.

ConsoleApp contains IService interface with an implementation class Service, and Class1 that consumes a service. In the main method ProcessValue method is run over several values.

ConsoleApp.Tests test library contains considered above tests.


1. All used IP-addresses, names of servers, workstations, domains, are fictional and are used exclusively as a demonstration only.
2. Information is provided «AS IS».

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.