7

I want to moq a property that has an index, and I want to be able to use the index values in the callback, the same way you can use method arguments in the callback for moq'd methods. Probably easiest to demonstrate with an example:

public interface IToMoq
{
    int Add(int x, int y);
    int this[int x] { get; set; }
}

    Action<int, int> DoSet = (int x, int y) =>
    {
        Console.WriteLine("setting this[{0}] = {1}", x, y);
        throw new Exception("Do I ever get called?"); 
    };
    var mock = new Mock<IToMoq>(MockBehavior.Strict);

    //This works perfectly
    mock.Setup(m => m.Add(It.IsAny<int>(), It.IsAny<int>()))
        .Returns<int, int>((a, b) => a + b);

    //This compiles, but my callback never seems to be called
    mock.SetupSet(m => m[It.IsAny<int>()] = It.IsAny<int>())
        .Callback(DoSet);

    var obj = mock.Object;
    Console.WriteLine("Add(3,4) => {0}", obj.Add(3, 4));  //Works perfectly
    obj[1] = 2;   //Does not throw, why? 

Edit: To clarify, I want the callback/returns method for the get to be Func<int,int>, and the callback/returns method for the set to be Action<int,int>. Trying what Mike suggested, you can sort of do this for set, but with one major limitation:

mock.SetupSet(m => m[23] = It.IsAny<int>())
            .Callback(DoSet).Verifiable();

The callback DoSet is indeed then called with values (23,<any>). Unfortunately, using It.IsAny<int>() instead of 23 seems to behave as 0, rather than <any>.

Also, I couldn't find a way of calling SetupGet with Returns where Returns takes a Func<int,int> that would even compile.

Is it possible to use Moq for this?

Motivation: I'm just playing with Moq, attempting to use it to provide a fluent API for performing interception. That is, given an interface I and an instance X of I, automatically create a Mock<I> proxy with behaviour defaulting to X.

Would probably make more sense to work directly with Castle DP, but I like the Moq Expression Tree syntax.

Rob
  • 4,327
  • 6
  • 29
  • 55
  • Seems like a bug to me. Setting up the mock with `mock.SetupSet(c => c[1] = It.IsAny()).Callback(DoSet);` works as expected, `mock.SetupSet(c => c[It.IsAny()] = It.IsAny()).Callback(DoSet);` fails with `MockBehavior.Strict` and does nothing with `MockBehavior.Loose`. – sloth Apr 17 '15 at 12:18
  • With `mock.SetupSet(x => x[It.IsAny()] = It.IsAny()).Callback(doSet);` and `obj[0] = 1; obj[1] = 2;` it works as expected for the zero-index. It fails on index 1 (or anything else for that matter). – transporter_room_3 Apr 17 '15 at 12:25
  • @Rob: What version of Moq are you running? I actually ran into two different behaviors. I'm checking the version of Moq I ran in two separate projects and one seems to work and the other does not. – Mike Bailey Apr 17 '15 at 13:57
  • @Mike - I'm using two versions also, though to try to get this working I temporarily upgraded to 4.2.1502 – Rob Apr 17 '15 at 14:02
  • @Rob: By the way, the problem is not due to Moq returning a value of `0`. There seems be an issue in the library itself that it won't handle `It.IsAny` with set indexers. I am not sure why but you can try opening an issue on their [Github](https://github.com/Moq/moq4/issues). – Mike Bailey Apr 17 '15 at 14:34

2 Answers2

1

As of Moq 4.2.1502.0911 for .NET Framework 4.5, I found the following behavior to be true.

The problem you're running into is specifically due to the It.IsAny<T> call. If you use It.IsAny<T>, the callback does not execute:

[Test]
public void DoesNotExecuteCallback()
{
    // Arrange
    var mock = new Mock<IIndexable>();

    var called = false;
    mock.SetupSet(m => m[It.IsAny<int>()] = It.IsAny<int>()).Callback<int,int>((x, y) => called = true);

    var instance = mock.Object;

    // Act
    instance[1] = 2;

    // Arrange
    Assert.That(called, Is.False);
}

But if you call it using specific parameters, it calls the callback:

[Test]
public void DoesExecuteCallback()
{
    // Arrange
    var mock = new Mock<IIndexable>();

    var called = false;
    mock.SetupSet(m => m[1] = 2).Callback<int,int>((x, y) => called = true);

    var instance = mock.Object;

    // Act
    instance[1] = 2;

    // Arrange
    Assert.That(called, Is.True);
}

To summarize, you should avoid the use of the It.IsAny when trying to setup expectations for an indexer. In general, you should be avoiding the use of it because it can encourage sloppy test writing

There are appropriate use cases for It.IsAny, but without knowing the specifics I tend to recommend against it.

Mike Bailey
  • 12,479
  • 14
  • 66
  • 123
  • I should say that what I am using Moq for can in no way be considered unit testing. – Rob Apr 17 '15 at 14:17
  • 1
    Can you be a bit more specific about what you're trying to do? While this one very specific scenario doesn't seem to be supported, if you post what you're really trying to do we might be able to find an alternative. – Mike Bailey Apr 17 '15 at 14:35
1

The method SetupSet takes a plain delegate, not an expression tree of type Expression<...> like many other Moq methods.

For that reason Moq cannot see that you used It.IsAny. Instead, It.IsAny is called (not what we want) and Moq sees its return value only which happens to be default(int), or 0.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181