Usually, when creating unit tests it’s best to keep it as simple as possible, so whenever the tests fail, it’s simple to identify the reason. However that’s not always possible. Sometimes our code is not fully testable, resulting in complex setups, for example. That can cause us to use more complicated mockings to achieve our objective.
TL;DR – Using Moq setup to return a property of the parameter
If you are only looking for a sample code, look no further:
mock.Setup(x => x.Method(It.IsAny<IInterface>())).Returns<IInterface>(x=> x.Property);
Detailed explanation
Scenario
Let’s suppose we are creating an application to list and sell plots. The flow starts by adding plots to the PlotSellingPage class. Whenever we add a plot to the PlotSellingPage, we call the PlotPriceCalculator to calculate its price. Let’s take a look at our code files.
Plot
public interface IPlot
{
double SquareMeters { get; }
}
public class SquarePlot : IPlot
{
public double SquareMeters { get; }
public SquarePlot(double size)
{
SquareMeters = size * size;
}
}
PlotPriceCalculator
public interface IPlotPriceCalculator
{
double GetPrice(IPlot plot);
}
public class PlotPriceCalculator : IPlotPriceCalculator
{
public double GetPrice(IPlot plot)
{
// Let's pretend some really crazy logic goes on in here
}
}
PlotSellingPage
public class PlotSellingPage
{
private readonly IPlotPriceCalculator _plotPriceCalculator;
private Dictionary<IPlot, double> _plotsForSale = new();
public PlotSellingPage(IPlotPriceCalculator plotPriceCalculator)
{
_plotPriceCalculator = plotPriceCalculator;
}
public void RegisterPlotForSale(IPlot plot)
{
var price = _plotPriceCalculator.GetPrice(plot);
_plotsForSale.Add(plot, price);
}
public double GetValueSum()
{
return _plotsForSale.Sum(x => x.Value);
}
}
Testing the GetValueSum method
Our task here is to verify that the GetValueSum does its job correctly. In order to do it, we are creating several plots and adding them to the PlotSellingPage, while also adding to the expectedSum variable to assert later that the actual value is correct.
[Test]
public void ShouldSumCorrectly()
{
// Arrange
var calculatorMock = new Mock<IPlotPriceCalculator>();
calculatorMock.Setup(x => x.GetPrice(It.IsAny<IPlot>())).Returns<IPlot>(plot => plot.SquareMeters);
var page = new PlotSellingPage(calculatorMock.Object);
double expectedSum = 0;
var random = new Random();
for (int i = 0; i < 10; i++)
{
var size = random.Next(0, 10);
var plot = new SquarePlot(size);
expectedSum += plot.SquareMeters;
page.RegisterPlotForSale(plot);
}
// Act
var actualSum = page.GetValueSum();
// Assert
Assert.That(actualSum, Is.EqualTo(expectedSum));
}
The trick lies in the setup on line 6: every time we call the GetPrice method in the PlotPriceCalculator mock, we are returning the SquareMeters property of the IPlot parameter provided to it. That is a very simple but logical setup for it, that allows us to prepare our class without exposing any unnecessary fields or properties. Be careful to not get tricked by the “<IPlot>” in “Returns<IPlot>”, as we might expect it to refer to the returning type when it actually refers to the input parameter type.
Another possible solution for this situation would be to use a Fake IPlotPriceCalcuator. Fakes are classes that have simpler and working implementations, allowing us to avoid working around the complex logic of production ones. You can read more about fakes, mocks and stubs here.