Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moq setup won't work for a method call followed by an implicit conversion

For this question consider (create) the interface:

public interface ITestMe
{
  string TakeInt64(long x);
}

Then run the following code:

public void Test()
{
  var mock1 = new Mock<ITestMe>(MockBehavior.Strict);
  Expression<Func<ITestMe, string>> expr1 = x => x.TakeInt64(2);
  Console.WriteLine(expr1);
  mock1.Setup(expr1).Returns("OK");
  var s1 = mock1.Object.TakeInt64(2L); // OK

  var mock2 = new Mock<ITestMe>(MockBehavior.Strict);
  Expression<Func<ITestMe, string>> expr2 = x => x.TakeInt64(DateTime.Today.Year / 1000);
  Console.WriteLine(expr2);
  mock2.Setup(expr2).Returns("OK");
  var s2 = mock2.Object.TakeInt64(2L); // OK

  var mock3 = new Mock<ITestMe>(MockBehavior.Strict);
  Expression<Func<ITestMe, string>> expr3 = x => x.TakeInt64((int)(DayOfWeek)Enum.Parse(typeof(DayOfWeek), "Tuesday"));
  Console.WriteLine(expr3);
  mock3.Setup(expr3).Returns("OK");
  var s3 = mock3.Object.TakeInt64(2L); // OK

  var mock4 = new Mock<ITestMe>(MockBehavior.Strict);
  Expression<Func<ITestMe, string>> expr4 = x => x.TakeInt64(GetInt32());
  Console.WriteLine(expr4);
  mock4.Setup(expr4).Returns("OK");
  //var s4 = mock4.Object.TakeInt64(2L); // MockException, All invocations on the mock must have a corresponding setup.

  var mock5 = new Mock<ITestMe>(MockBehavior.Strict);
  Expression<Func<ITestMe, string>> expr5 = x => x.TakeInt64(int.Parse("2"));
  Console.WriteLine(expr5);
  mock5.Setup(expr5).Returns("OK");
  //var s5 = mock5.Object.TakeInt64(2L); // MockException, All invocations on the mock must have a corresponding setup.

  var mock6 = new Mock<ITestMe>(MockBehavior.Strict);
  Expression<Func<ITestMe, string>> expr6 = x => x.TakeInt64(GetInt32() + 0);
  Console.WriteLine(expr6);
  mock6.Setup(expr6).Returns("OK");
  var s6 = mock6.Object.TakeInt64(2L); // OK

  var mock7 = new Mock<ITestMe>(MockBehavior.Strict);
  Expression<Func<ITestMe, string>> expr7 = x => x.TakeInt64(1 * int.Parse("2"));
  Console.WriteLine(expr7);
  mock7.Setup(expr7).Returns("OK");
  var s7 = mock7.Object.TakeInt64(2L); // OK
}

static int GetInt32()
{
  return 2;
}

In all seven cases we make an expression tree where the TakeInt64 gets an int (Int32) instead of a long (Int64). However, as is well known, there exists an implicit conversion from int to long which will be present in the expression tree (except in expr1 where the compiler converts the constant for us).

How come the cases s4 and s5 above won't work? Curiously, as you can see, if we add 0 or multuply by 1 as in cases s6 and s7, that works (even if the type is still int, implicitly converted to long)?

Please answer before year 3000 because of case expr2.

like image 215
Jeppe Stig Nielsen Avatar asked Sep 18 '13 15:09

Jeppe Stig Nielsen


1 Answers

I think this is a bug in Moq. The relevant code is in MatcherFactory. Specifically, the Convert is removed from the expression so that it can be inspected further. When the remaining topmost expression node is a method call, this node is evaluated lazily. When the remaining expression is not a method call, the whole expression (including Convert!) is evaluated eagerly.

This means that with lazy evaluation, the comparison is done without the Convert and object.Equals(2, 2L) is false. But with eager evaluation, the Convert is taken into account and so your code works.

I have made an attempt at fixing this, which seems to fix the issue for me.

(Whew, I almost thought I wouldn't make it in time.)

like image 186
svick Avatar answered Sep 23 '22 07:09

svick