Ilya Chernomordik
Ilya Chernomordik

Reputation: 30355

Moq setup treats all empty enumerables/arrays as the same parameter

I have a method that accepts an IEnumerable:

MyMethod(IEnumerable<MyClass> myParameter)

Now I am writing this code to mock the service:

var array1 = new MyClass[0];
var array2 = new MyClass[0];

_service
   .Setup(s => s.MyMethod(array1))
   .Returns(value1);

_service
   .Setup(s => s.MyMethod(array2))
   .Returns(value2);

And finally I am doing two calls to the service with both arrays inside system under test:

_service.MyMethod(array1);
_service.MyMethod(array2);

What I do expect is to get value1 and value2 from these calls, but in practice the latter call overrides the first one and I only get value2 from both calls.

Is this a bug in Moq or is this a feature that setup treats IEnumerable not as a separate object but rather tries to expand it and compare all elements or something (resulting in two empty arrays being the same setup call)?

Upvotes: 3

Views: 627

Answers (2)

Johnny
Johnny

Reputation: 9519

The behaviour you describe is the default behaviour of the moq, you can see it here. It indeed unfold enumerable and invoke IEnumerable.SequenceEqual. However that is default behaviour(if you setup using an instance, Constant matcher) and you could override it. The one approach is what Owen suggested to use It.Is<T> matcher, e.g.

service.Setup(s => s.MyMethod(It.Is<IEnumerable<MyClass>>(e => e == array1)))
service.Setup(s => s.MyMethod(It.Is<IEnumerable<MyClass>>(e => e == array2)))

Notice that == by default do ReferenceEquals() so this will make different non overridable setups.

Upvotes: 2

Owen Pauling
Owen Pauling

Reputation: 11871

When you create multiple setups on a method with Moq, each subsequent setup will replace the previous setup unless the setups are conditional (they specify certain conditions on the arguments). See this answer.

You can fix your code by specifying that the arguments must match the ones you intend to pass:

[Test]
public void MyTest()
{
    var service = new Mock<MyClass>();
    var array1 = new MyClass[0];
    var array2 = new MyClass[0];

    var value1 = "value1";
    var value2 = "value2";

    service.Setup(s => s.MyMethod(It.Is<IEnumerable<MyClass>>(e => e == array1))).Returns(value1);          
    service.Setup(s => s.MyMethod(It.Is<IEnumerable<MyClass>>(e => e == array2))).Returns(value2);

    Assert.AreEqual(value1, service.Object.MyMethod(array1));
    Assert.AreEqual(value2, service.Object.MyMethod(array2));
}

Upvotes: 2

Related Questions