Repository: devlooped/moq

Issue:
Issue comparing Expressions when one value is a constant
I have discovered an issue when using Expressions in a Setup on a mock object.

It seems using a simple comparison Expression fails to match properly when comparing to a string constant.

The below code should reproduce it:
```
[Test]
public async Task ExpressionTest()
{
    var s1 = "example";

    // Does not work
    const string s2 = "example";

    // Works
    //var s2 = "example";

    var mock = new Mock<IMockObject>();

    mock.Setup(x => x.Method(y => y.Id == s1))
        .ReturnsAsync("mockResult");

    var result = await mock.Object.Method(x => x.Id == s2);
    // Exprect result to be "mockResult", instead it is null

    Assert.AreEqual(result, "mockResult");
}

public class MyObject
{
    public string Id { get; set; }
}

public interface IMockObject
{
    public Task<string> Method(Expression<Func<MyObject, bool>> expression);
}

```

The two strings s1 and s2 are the same, however when one of them is a constant then it fails to match the two expressions.

I would expect it should work the same when the string is a constant?

This is on a .NET Core 3.1 test project using Moq 4.14.5 and NUnit 3.12.


Hints:
For now I can work around by simply assigning a local variable in the test from the constant and using that instead.
Referencing a constant vs. a variable results in slightly different LINQ expression trees: in the former case, the string value gets embedded as a constant, while in the latter case, the variable gets captured in the form of a reference to an anonymous object's field.

We've had a few similar issues in the past, and the approach followed most recently was that captured variables should be evaluated down to a constant value at the time when two LINQ expression trees get compared.

If we applied this rule here, this present issue would be resolved.

However, at present I am not sure whether this rule *should* apply in this case. The added complication here is that we have a setup LINQ expression tree that embeds another LINQ expression tree (the argument passed to `Method`)... and we need to know whether we're allowed to "touch" (here: partially reduce) embedded expression trees or whether we should treat them as constants and always leave them unmodified.

This is probably related to the two principal ways how expression trees can reference other expression trees: [`Expression.Constant` and `Expression.Quote`](https://stackoverflow.com/q/3716492). I'm not positively certain that Moq makes this distinction correctly in all situations.

TL;DR: I'll look into it, but this will take some thought and time.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eb4b92aad..129245883 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
 #### Fixed
 
 * `SetupProperty` fails if property getter and setter are not both defined in mocked type (@stakx, #1017)
+* Expression tree argument not matched when it contains a captured variable &ndash; evaluate all captures to their current values when comparing two expression trees (@QTom01, #1054)
 
 
 ## 4.14.7 (2020-10-14)
diff --git a/src/Moq/ExpressionComparer.cs b/src/Moq/ExpressionComparer.cs
index 693feafd2..a86e68574 100644
--- a/src/Moq/ExpressionComparer.cs
+++ b/src/Moq/ExpressionComparer.cs
@@ -31,6 +31,19 @@ public bool Equals(Expression x, Expression y)
 				return false;
 			}
 
+			// Before actually comparing two nodes, make sure that captures variables have been
+			// evaluated to their current values (as we don't want to compare their identities):
+
+			if (x is MemberExpression)
+			{
+				x = x.Apply(EvaluateCaptures.Rewriter);
+			}
+
+			if (y is MemberExpression)
+			{
+				y = y.Apply(EvaluateCaptures.Rewriter);
+			}
+
 			if (x.NodeType == y.NodeType)
 			{
 				switch (x.NodeType)
@@ -200,21 +213,7 @@ private bool EqualsMemberBinding(MemberBinding x, MemberBinding y)
 
 		private bool EqualsMember(MemberExpression x, MemberExpression y)
 		{
-			// If any of the two nodes represents an access to a captured variable,
-			// we want to compare its value, not its identity. (`EvaluateCaptures` is
-			// a no-op in all other cases, so it is safe to apply "just in case".)
-			var rx = x.Apply(EvaluateCaptures.Rewriter);
-			var ry = y.Apply(EvaluateCaptures.Rewriter);
-
-			if (rx == x && ry == y)
-			{
-				return x.Member == y.Member && this.Equals(x.Expression, y.Expression);
-			}
-			else
-			{
-				// Rewriting occurred, we might no longer have two `MemberExpression`s:
-				return this.Equals(rx, ry);
-			}
+			return x.Member == y.Member && this.Equals(x.Expression, y.Expression);
 		}
 
 		private bool EqualsMemberInit(MemberInitExpression x, MemberInitExpression y)


Test patch:
diff --git a/tests/Moq.Tests/Regressions/IssueReportsFixture.cs b/tests/Moq.Tests/Regressions/IssueReportsFixture.cs
index 0416c4bad..337376404 100644
--- a/tests/Moq.Tests/Regressions/IssueReportsFixture.cs
+++ b/tests/Moq.Tests/Regressions/IssueReportsFixture.cs
@@ -3451,6 +3451,52 @@ public class DatabaseOptions
 
 		#endregion
 
+		#region 1054
+
+		public class Issue1054
+		{
+			// These tests assert that Moq compares the values of captured variables
+			// (as opposed to their identities).
+
+			[Fact]
+			public void String_constant()
+			{
+				var s1 = "example";
+				const string s2 = "example";
+
+				var mock = new Mock<IMockObject>();
+				mock.Setup(x => x.Method(y => y.Id == s1)).Returns("mockResult");
+				var result = mock.Object.Method(x => x.Id == s2);
+
+				Assert.Equal("mockResult", result);
+			}
+
+			[Fact]
+			public void String_variable()
+			{
+				var s1 = "example";
+				var s2 = "example";
+
+				var mock = new Mock<IMockObject>();
+				mock.Setup(x => x.Method(y => y.Id == s1)).Returns("mockResult");
+				var result = mock.Object.Method(x => x.Id == s2);
+
+				Assert.Equal("mockResult", result);
+			}
+
+			public class MyObject
+			{
+				public string Id { get; set; }
+			}
+
+			public interface IMockObject
+			{
+				public string Method(Expression<Func<MyObject, bool>> expression);
+			}
+		}
+
+		#endregion
+
 		#region 1071
 
 		public class Issue1071