如何用不同的 lambda 表达式模拟一个方法两次?

How mock a method two times with different lambda expression?

我有一个调用存储库(使用 EF 的数据访问层)的管理器(业务层)。 管理器的逻辑将使用两个不同的 lambda 表达式作为参数调用存储库的方法两次。

我的问题是如何将我的存储库模拟为 return 第一个 lambda 的给定响应,但 return 第二个 lambda 的另一个响应?

例如:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Relation
{
    public int GiverId { get; set; }
    public int ReceiverId { get; set; }
}

public interface IRelationRepository
{
    bool Loves(Expression<Func<Relation, bool>> predicate);
}

public class RelationRepository : IRelationRepository
{
    public bool Loves(Expression<Func<Relation, bool>> predicate)
    {
        // Some logic...
        return true;
    }
}

public class KissManager
{
    private readonly IRelationRepository repository;

    public KissManager(IRelationRepository repository)
    {
        this.repository = repository;
    }

    public bool Kiss(Person p1, Person p2)
    {
        var result = this.repository.Loves(r => r.GiverId == p1.Id && r.ReceiverId == p2.Id)
            && this.repository.Loves(r => r.GiverId == p2.Id && r.ReceiverId == p1.Id);
        return result;
    }
}

[TestMethod]
public void KissWithReceiverNotInLove()
{
    // Arange.
    var p1 = new Person { Id = 5, Name = "M. Love" };
    var p2 = new Person { Id = 17, Name = "Paul Atreid" };

    var kissRepositoryMock = new Mock<IRelationRepository>();
    kissRepositoryMock
        .Setup(m => m.Loves(r => r.GiverId == p1.Id && r.ReceiverId == p2.Id))
        .Returns(true);
    kissRepositoryMock
        .Setup(m => m.Loves(r => r.GiverId == p2.Id && r.ReceiverId == p1.Id))
        .Returns(false);

    var kissManager = new KissManager(kissRepositoryMock.Object);

    // Act.
    var result = kissManager.Kiss(p1, p2);

    // Assert.
    Assert.IsFalse(result);
}

一个选项是设置方法以通过 It.IsAny<Expression<Func<Relation, bool>>>() 接受任何 expression/predicate 并使用允许给定 predicate/expression.[=12 的所需行为的假数据源=]

[TestMethod]
public void _KissWithReceiverNotInLove() {
    // Arange.
    var p1 = new Person { Id = 5, Name = "M. Love" };
    var p2 = new Person { Id = 17, Name = "Paul Atreid" };
    var relations = new List<Relation>()
    {
        new Relation{ GiverId = p1.Id, ReceiverId = p2.Id }
        //The above should satisfy the first expected predicate.
        //Note there are no other relations in this list.
        //That is by design to make the second predicate return false.
    };

    var kissRepositoryMock = new Mock<IRelationRepository>();

    kissRepositoryMock
        .Setup(m => m.Loves(It.IsAny<Expression<Func<Relation, bool>>>()))
        .Returns((Expression<Func<Relation, bool>> predicate) => relations.Any(predicate.Compile()));

    var kissManager = new KissManager(kissRepositoryMock.Object);

    // Act.
    var result = kissManager.Kiss(p1, p2);

    // Assert.
    Assert.IsFalse(result);
}

正如第一个 post 的评论中所说,也可以使用 SetupSequence,但我更喜欢@Nkosi 解决方案,因为它不依赖于实现。

我 post 在我们的示例中使用 SetupSequence 的示例。它可能对某些人有用。

[TestMethod]
public void KissWithReceiverNotInLove()
{
    // Arange.
    var p1 = new Person { Id = 5, Name = "M. Love" };
    var p2 = new Person { Id = 17, Name = "Paul Atreid" };

    var kissRepositoryMock = new Mock<IRelationRepository>();
    kissRepositoryMock
        .SetupSequence(m => m.Loves(It.IsAny<Expression<Func<Relation, bool>>>()))
        .Returns(true)
        .Returns(false);

    var kissManager = new KissManager(kissRepositoryMock.Object);

    // Act.
    var result = kissManager.Kiss(p1, p2);

    // Assert.
    Assert.IsFalse(result);
}