Timothy Hayes
Timothy Hayes

Reputation: 55

Use of SetupSet 'forgets' Method Setup

While I was experimenting to resolve a different situation with Moq, I attempted to use SetupSet to resolve. This uncovered another potential problem.

When I use SetupSet on a property along with a Setup on a Method, Moq seems to 'forget' that the Setup on the Method has been done.

Here is sample code, very simple:

public class Prancer
{

    public Prancer(bool pIsMale)
    {
        IsMale = pIsMale;
        ExecuteMe();
    }

    private bool _IsMale;
    public virtual bool IsMale
    {
        get { return this._IsMale; }
        private set { this._IsMale = value; }
    }

    private bool _Antlers;
    public virtual bool Antlers
    {
        get { return this._Antlers; }
        set
        {
            this._Antlers = value;
        }
    }

    public virtual void ExecuteMe()
    {
        throw new Exception("Why am I here?");
    }
}

Here are the unit tests:

public class PrancerTests
{
    [Fact]
    public void Antlers_NoSetup()
    {
        // Arrange

        // create mock of class under test
        var sut = new Mock<Prancer>(true) { CallBase = true };
        sut.Setup(x => x.ExecuteMe()); // nullify

        // Act
        sut.Object.Antlers = true;

        // Assert
        sut.VerifySet(x => x.Antlers = true);
    }

    [Fact]
    public void Antlers_SetupProperty()
    {
        // Arrange

        // create mock of class under test
        var sut = new Mock<Prancer>(true) { CallBase = true };
        sut.SetupProperty(x => x.Antlers, false);
        sut.Setup(x => x.ExecuteMe()); // nullify

        // Act
        sut.Object.Antlers = true;

        // Assert
        sut.VerifySet(x => x.Antlers = true);
    }

    [Fact]
    public void Antlers_SetupSet()
    {
        // Arrange

        // create mock of class under test
        var sut = new Mock<Prancer>(true) { CallBase = true };
        sut.SetupSet(x => x.Antlers = true);
        sut.Setup(x => x.ExecuteMe()); // nullify

        // Act
        sut.Object.Antlers = true;

        // Assert
        sut.VerifySet(x => x.Antlers = true);
    }

}

The unit test where I use SetupSet reports the Exception ("Why am I here?") thrown in method ExecuteMe() which proves that the method ExecuteMe() executed even when there was a Setup(x => x.ExecuteMe()) to prevent it. The other two unit tests pass (and apparently do not execute ExecuteMe()).

I even attempted to put a Callback on the Setup for ExecuteMe(), but same result. I also reversed the order (in code) of the Setup and SetupSet, to no avail.

Any thoughts why the SetupSet could have affected the method Setup?

Upvotes: 4

Views: 234

Answers (1)

Any thoughts why the SetupSet could have affected the method Setup?

I believe this is a bug in Moq. Would you please be so kind and file an issue at Moq's GitHub repository moq/moq4? (Just include the code you posted here, or link to this SO question.)

I'll try to explain what's going on here. (This will sound familiar to you since you've already reported a similar problem on GitHub; I'm repeating the explanation here for the sake of SO visitors.) Let's start by looking at your call to SetupSet:

sut.SetupSet(x => x.Antlers = true);

Moq makes heavy use of LINQ expression trees (Expression<Action<TMock,…>> or Expression<Func<TMock,…>>) in its setup and verify methods. Expressions are simply "code as data" which Moq can analyze to figure out what you want your mock to do (during setup), or what should have happened with your mock (during verification).

However, due to a limitation with the C# compiler (namely that it cannot transform lambdas containing an assignment to an expression tree), Moq's SetupSet cannot use expression trees; instead, it accepts a plain Action<TMock>, i.e. a piece of code that cannot be analyzed directly. Yet Moq needs to perform a setup based on this piece of code. What happens in this case is that Moq invokes this lambda in a recorder-like "dry run" mode (internally known as FluentMockContext). It then observes the effects caused by that "dry run" and bases its setup actions on them.

Now we're getting to a point mentioned by @Kritner in a comment above:

SetupSet goes through the constructor, where none of your other setup/verifies do.

Invoking the delegate implies that it has to actually instantiate the mock object so it can pass it to your setup lambda as the argument. This means that your mocked type's constructor will run. And because you've specified CallBase = true, during that dry run, Moq will call your ExecuteMe base implementation. And that's why we end up in your method that throws.

The bug here is the fact that CallBase doesn't really work with that "dry run" principle, because the whole purpose of CallBase is to execute user code in the mocked type, which lies outside the control of Moq, and therefore doesn't know (and rightly shouldn't ever have to know) that it should execute in a "dry run" mode.

The whole "dry run" simulation mode works well for many common usage scenarios, but is fundamentally flawed. I've identified quite a few problems with Moq that are caused by it and I've been looking into ways (method decompilation among others) to replace the internal component (FluentMockContext) used for it.

Please file this as a bug on Moq's GitHub repository, I'll add it to that list of problems.

Upvotes: 1

Related Questions