Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to Moq Fluent interface / chain methods

I'm using the moq framework by Daniel Cazzulino, kzu Version 4.10.1. I want to moq so i can test a particular part of functionality (below is the simplistic version of the Code i could extract)

The fluent/chain method so are designed so you can get object by an Id and include any additional information if required.

i'm having some trouble fetching the correct object when the function is calling the moq'ed method, which is currently returning the last moq'ed object which is wrong

 /*My current Moq setup*/

class Program
{
    static void Main(string[] args)
    {
        var mock = new Mock<IFluent>();

        var c1 = new ClassA() { Id = 1, Records = new List<int>() { 5, 2, 1, 10 }, MetaData = new List<string>() };
        var c2 = new ClassA() { Id = 2, Records = new List<int>(), MetaData = new List<string>() { "X", "Y", "Z" } };

        mock.Setup(x => x.GetById(1).IncludeRecords().IncludeMetaData().Get()).Returns (c1);
        mock.Setup(x => x.GetById(2).IncludeRecords().IncludeMetaData().Get()).Returns(c2);

        var result = new ComputeClass().ComputeStuff(mock.Object);
        Console.WriteLine(result);
        Console.ReadLine();
    }
}

/*Fluent interface and object returned*/
public interface IFluent
{
    IFluent GetById(int id);
    IFluent IncludeRecords();
    IFluent IncludeMetaData();
    ClassA Get();
}
public class ClassA
{
    public int Id { get; set; }
    public ICollection<int> Records { get; set; }
    public ICollection<string> MetaData { get; set; }
}

 /*the method which is doing the work*/
public class ComputeClass
{
    public string ComputeStuff(IFluent fluent)
    {
        var ids = new List<int>() { 1, 2 };
        var result = new StringBuilder();
        foreach (var id in ids)
        {
            var resClass = fluent.GetById(id).IncludeRecords().IncludeMetaData().Get();
            result.Append($"Id : {id}, Records: {resClass.Records.Count}, MetaData: {resClass.MetaData.Count}{Environment.NewLine}");
        }
        return result.ToString();
    }
}

   Current incorrect result 
    /*Id : 1, Records: 0, MetaData: 3
      Id : 2, Records: 0, MetaData: 3*/

     Expected Result
    /*Id : 1, Records: 3, MetaData: 0
      Id : 2, Records: 0, MetaData: 3*/
like image 627
UndeadEmo Avatar asked May 16 '19 10:05

UndeadEmo


2 Answers

The easiest way would be to split out each setup:

var mock = new Mock<IFluent>();
var mock1 = new Mock<IFluent>();
var mock2 = new Mock<IFluent>();

mock.Setup(x => x.GetById(1)).Returns(mock1.Object); 
mock1.Setup(x => x.IncludeRecords()).Returns(mock1.Object);
mock1.Setup(x => x.IncludeMetaData()).Returns(mock1.Object);
mock1.Setup(x => x.Get()).Returns(c1);

mock.Setup(x => x.GetById(2)).Returns(mock2.Object); 
mock2.Setup(x => x.IncludeRecords()).Returns(mock2.Object);
mock2.Setup(x => x.IncludeMetaData()).Returns(mock2.Object);
mock2.Setup(x => x.Get()).Returns(c2);

var result = new ComputeClass().ComputeStuff(mock.Object);

You could create an extension/utility to handle this all for you if you wanted something a bit more complex, take a look at this blog post: https://www.codemunki.es/2014/11/20/mocking-a-fluent-interface-automatically-in-moq/

like image 174
andyb952 Avatar answered Oct 17 '22 22:10

andyb952


Just an addition to the already existing answer. For mocking fluent API there is one usefull option within the moq, it is SetReturnsDefault, it could save some mocking especially if you have huge fluent API, e.g.

var mock = new Mock<IFluent>();
var mock1 = new Mock<IFluent>();
var mock2 = new Mock<IFluent>();

mock.Setup(x => x.GetById(1)).Returns(mock1.Object);
mock1.SetReturnsDefault(mock1.Object);
mock1.Setup(x => x.Get()).Returns(a);

mock.Setup(x => x.GetById(2)).Returns(mock2.Object);
mock2.SetReturnsDefault(mock2.Object);
mock2.Setup(x => x.Get()).Returns(b);

var aa = mock.Object.IncludeMetaData().GetById(1).IncludeMetaData().Get();
var bb = mock.Object.IncludeMetaData.GetById(2).IncludeMetaData.Get();

With this approach you actually have to mock only method which differ but not the all methods from fluent API.

like image 34
Johnny Avatar answered Oct 18 '22 00:10

Johnny