【问题标题】:Moq verify uses objects modified in Returns, rather than what was actually passed inMoq verify 使用在 Returns 中修改的对象,而不是实际传入的对象
【发布时间】:2016-06-27 20:04:06
【问题描述】:

背景

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

问题

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

为了说明,这是一个非常基本的类(我重写了 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)));
    }
}

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

【问题讨论】:

    标签: c# unit-testing moq


    【解决方案1】:

    您可以切换它以验证是否使用正确的对象完成了保存。然后断言 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 为零的对象调用的。

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

    这应该满足您想要实现的目标。

    【讨论】:

    • 只测试前后状态,可以通过多种不同的方式完成。它不会测试是否将正确的对象传递给 Save 函数。事实上,我还有其他测试与您在这里所做的完全一样,但是我有一个错误,我一直在创建新对象,而不是使用现有对象,所以我需要一种方法来确保我是传入正确的对象。
    • mock.Verify(x =&gt; x.Save(sut)); 会检查是否传递了正确的对象。你用 is any 设置它,然后你确认正确的对象,在这种情况下是 Class1 sut 被传递给它
    • 有点...除了当您验证它时,'sut' 具有更新的 ID。我想测试传入的对象的 ID 是否为零。此测试无法验证这一点。
    • @MattZappitello 那么您需要更改您的设置以及如何进行验证。检查更新的答案。
    • 如果你认为它应该支持你正在尝试做的事情,你可以在 GitHub 上为它记录一个问题。也许开发人员可以更好地洞察问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-25
    • 2012-09-21
    • 2013-01-22
    相关资源
    最近更新 更多