Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Background

I have a class that uses NHibernate to persist objects to a database. When you call MergeEntity for an object that does not have an ID set, NHibernate populates that object with an ID when it is returned. In order to make sure I always use the same object as NHibernate is using, I pass that updated object back from my "Save" function.

Problem

I am trying to mock that same behavior using Moq, which is usually very intuitive and easy to use; however, I am having some trouble verifying that the calls to Save() are being made with the correct arguments. I would like to verify that the ID of the object being passed in is zero, then that it is set properly by the Save function. Unfortunately, when I modify the ID in the Moq.Returns() function, the Moq.Verify function uses the modified value rather than the value of the ID that was passed in.

To illustrate, here is a very basic class (I override the ToString() function so my test output will show me what ID was used when the mocked Save() is called):

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

Here is the Interface (very straight forward):

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

And here is the test that I think should pass:

[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)));
    }
}

Unfortunately, it fails saying that it was never called with arguments that match that criteria. The only invocation to Save that was made on the mock is with an object that had an ID of 1. I have verified that the objects have an ID of 0 when they enter the Returns function. Is there no way for me to distinguish what was passed into the mock from what those objects are updated to be if I update the values in my Returns() function?

like image 755
Matt Zappitello Avatar asked Nov 09 '22 13:11

Matt Zappitello


1 Answers

You can switch it up to verify that the Save was done with the correct object. And then assert that the Id was changed as expected.

[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);
}

By applying the filter on setup you and making it verifiable then you confirm that the method was actually called with an object that had an ID of zero.

I've tested this and it passes. To confirm that is works as expected you can change the id of sut from expected start id before doing act. Verify would fail as it does not match predicate.

This should satisfy what you are trying to achieve.

like image 65
Nkosi Avatar answered Nov 14 '22 21:11

Nkosi