user2343647
user2343647

Reputation: 661

How to capture the setting of a property value with Mock (Moq)

In my test project, I want to capture a property set by the SUT to a mocked object. I have tried many things, but none seem to allow me to capture that.

I set up a short example:

The mocked interface:

public interface ISomeInterface
{
    string SomeProperty { get; set; }
}

The SUT:

public class SomeSystemUnderTest
{
    public void AssignSomeValueToThis(ISomeInterface obj)
    {
        obj.SomeProperty = Guid.NewGuid().ToString();
    }
}

The test:

[TestClass]
public class SomeTests
{
    [TestMethod]
    public void TestSomeSystem()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();

        someInterfaceMock.SetupSet(m => m.SomeProperty = It.IsAny<string>()).Verifiable();

        // Act
        var sut = new SomeSystemUnderTest();
        sut.AssignSomeValueToThis(someInterfaceMock.Object);

        // Assert
        // HERE I WOULD LIKE TO READ WHAT VALUE WAS ASSIGNED
        string myVal = someInterfaceMock.Object.SomeProperty;
    }
}

The "myVal" variable stays null, and by examining the mock, we can see the property is still null. I did not really expected it to have some value, just trying.

I tried with Setup, with a callback, I get compilation errors.

In real life project, the SUT is to transform a mocked object property to something dependant to another object property. To know if the object is doing its job, I need to be able to read the property. Note, I cannot re-designed the mocked interfaces, they are 3rd party.

I tried to use VerifySet, but it seems to take a hard-coded value only.

Thank you, Michel

Upvotes: 6

Views: 7154

Answers (2)

yoel halb
yoel halb

Reputation: 12711

General Answer

Moq actually has special methods for that:

  • To setup an individual property: mock.SetupProperty(m => m.SomeProperty) , optionally passing a default value
  • To setup all properties: mock.SetupAllProperties()

Specific Cases

There are however specific cases where this isn't enough, for example if we want to setup a property and give it a default value without setting the property value via the property setters.

(One example case, is when we need the property with the set value for use in the constructor, in which case we cannot set the property directly as it would require calling .Object which implicitly calls the constructor).

In this situation one should use the answer of @Johnny.

Here is a generic extension method version of his answer:

public static void SetupAutoProperty<TObject, TProperty>(this Mock<TObject> mock,
       Expression<Func<TObject, TProperty>> memberAccessExpr, 
       TProperty initialValue) where TObject : class
{
     var propStates = new List<TProperty>();            
     Expression<Action> captureExpression = () => Capture.In(propStates);

     var finalExpression = Expression.Lambda<Action<TObject>>(
                 Expression.Assign(memberAccessExpr.Body, captureExpression.Body),
                                        memberAccessExpr.Parameters);

      mock.SetupSet(finalExpression.Compile());
      mock.SetupGet(memberAccessExpr).Returns(() => 
                 propStates.Any() ? propStates.Last() : initialValue);
}

And call it as in someInterfaceMock.SetupAutoProperty(m => m.SomeProperty, someInitialValue)

Upvotes: 3

Johnny
Johnny

Reputation: 9509

There is a difference between get and set and mock actually doesn't have any internal state but only the setups which it tries to match and behave properly. You could mimic real get and set functionality by using callback. Something like this:

//Arrange
string someProperty = null;
var mock = new Mock<ISomeInterface>();

mock.SetupSet(m => m.SomeProperty = It.IsAny<string>())
    .Callback<string>(p => someProperty = p)
    .Verifiable();

// use func instead of value to defer the resulution to the invocation moment
mock.SetupGet(m => m.SomeProperty).Returns(() => someProperty);

//Act
mock.Object.SomeProperty = "test";

//Assert
Assert.AreEqual("test", mock.Object.SomeProperty);

The other possibility is to use Capture itself it actually exist within moq

//Arrange
List<string> someProperty = new List<string>();
var mock = new Mock<ISomeInterface>();

mock.SetupSet(m => m.SomeProperty = Capture.In(someProperty))
    .Verifiable();

mock.SetupGet(m => m.SomeProperty).Returns(() => someProperty.Last());

//Act
mock.Object.SomeProperty = "test";

//Assert
Assert.AreEqual("test", mock.Object.SomeProperty);

Upvotes: 10

Related Questions