如何使用Moq实现流畅接口/链式方法?

6
我正在使用由Daniel Cazzulino和kzu版本4.10.1开发的Moq框架。 我想使用Moq来测试功能的特定部分(下面是我能提取的简化代码版本)
流畅/链式方法被设计为您可以按ID获取对象,并在需要时包含任何其他信息。
当函数调用经过模仿的方法时,我遇到了一些问题,无法获取正确的对象。目前返回的是上一个被模仿的对象,这是错误的。
 /*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*/
2个回答

5
最简单的方法是将每个设置分开:
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);

如果你希望有一个处理所有这些的扩展/工具,可以让事情变得更加复杂一些,可以看看这篇博客文章:https://www.codemunki.es/2014/11/20/mocking-a-fluent-interface-automatically-in-moq/


嗨,这是有用的信息来模拟流畅,但我的当前问题是我有一个getById(2),它返回一个不同的对象。虽然如果你描述的方法有效,你能发表完整的答案吗?因为我刚刚尝试了一下,实际结果并不是预期的。 - UndeadEmo
上述编辑理论上应该可以工作。我自己还没有测试过。你可以试一下,如果不行的话,我会亲自运行它。 - andyb952
谢谢,加上缺失的分号后可以了 :) - UndeadEmo

5

给已有答案的补充说明:对于模拟流畅API,moq中有一个有用的选项,即SetReturnsDefault,它能够节省一些模拟成本,特别是当您有庞大的流畅API时。

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

通过这种方法,您实际上只需要模拟不同的方法而不是流畅API的所有方法。


酷,我没意识到你可以这样做! - andyb952

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接