Moq 验证使用在 Returns 中修改的对象,而不是实际传入的对象

Moq verify uses objects modified in Returns, rather than what was actually passed in

背景

我有一个 class,它使用 NHibernate 将对象持久保存到数据库中。当您为没有设置 ID 的对象调用 MergeEntity 时,NHibernate 会在返回时使用 ID 填充该对象。为了确保我始终使用与 NHibernate 正在使用的对象相同的对象,我将更新后的对象从我的“Save”函数传回。

问题

我正在尝试使用 Moq 模拟相同的行为,这通常非常直观且易于使用;但是,我在验证对 Save() 的调用是否使用正确的参数时遇到了一些麻烦。我想验证传入的对象的 ID 是否为零,然后它是否由 Save 函数正确设置。不幸的是,当我在 Moq.Returns() 函数中修改 ID 时,Moq.Verify 函数使用修改后的值而不是传入的 ID 的值。

为了说明,这是一个非常基本的 class(我覆盖了 ToString() 函数,所以我的测试输出将显示调用模拟的 Save() 时使用的 ID):

public class Class1
{
    private readonly IPersistence _persistence;

    /// <summary>Initializes a new instance of the <see cref="T:System.Object" /> class.</summary>
    public Class1(IPersistence persistence)
    {
        _persistence = persistence;
    }

    public int Id { get; set; }

    public void Save()
    {
       _persistence.Save(this);
    }

    public override string ToString()
    {
        return Id.ToString();
    }
}

这是界面(非常简单):

public interface IPersistence
{
    Class1 Save(Class1 one);
}

这是我认为应该通过的测试:

[TestFixture]
public class Class1Tests
{
    [Test]
    public void Save_NewObjects_IdsUpdated()
    {
        var mock = new Mock<IPersistence>();
        mock.Setup(x => x.Save(It.IsAny<Class1>()))
            .Returns((Class1 c) =>
            {
                // If it is a new object, then update the ID
                if (c.Id == 0) c.Id = 1;

                return c;
            });

        // Verify that the IDs are updated for new objects when saved
        var one = new Class1(mock.Object);

        Assert.AreEqual(0, one.Id);

        one.Save();

        mock.Verify(x => x.Save(It.Is<Class1>(o => o.Id == 0)));
    }
}

不幸的是,它没有说明从未使用符合该条件的参数调用它。在 mock 上对 Save 的唯一调用是使用 ID 为 1 的对象。我已验证对象在进入 Returns 函数时的 ID 为 0。如果我更新我的 Returns() 函数中的值,我是否无法区分传递给模拟的内容和这些对象更新后的内容?

您可以切换它以验证保存是使用正确的对象完成的。然后断言 Id 已按预期更改。

[Test]
public void Save_NewObjects_IdsUpdated() {
    //Arrange
    var expectedOriginalId = 0;
    var expectedUpdatedId = 1;
    var mock = new Mock<IPersistence>();
    mock.Setup(x => x.Save(It.Is<Class1>(o => o.Id == expectedOriginalId)))
        .Returns((Class1 c) => {
            // If it is a new object, then update the ID
            if (c.Id == 0) c.Id = expectedUpdatedId;

            return c;
        }).Verifiable();

    var sut = new Class1(mock.Object);
    var actualOriginalId = sut.Id;

    //Act
    sut.Save();

    //Assert

    //verify id was 0 before calling method under test
    Assert.AreEqual(expectedOriginalId, actualOriginalId);
    //verify Save called with correct argument
    //ie: an object that matched the predicate in setup
    mock.Verify();
    // Verify that the IDs are updated for new objects when saved
    Assert.AreEqual(expectedUpdatedId, sut.Id);
}

通过在设置中应用过滤器并使其可验证,然后您确认该方法实际上是使用 ID 为零的对象调用的。

我已经测试过了,它通过了。要确认是否按预期工作,您可以在执行操作之前从预期的开始 ID 更改 sut 的 ID。验证将失败,因为它与谓词不匹配。

这应该能满足您的目标。