Repository: devlooped/moq

Issue:
Mock.Of of Excel interop classes fails to initialize
I have a project that has a lot of tests on Excel interop classes.  
Those were working fine with my current test project (net452, Moq 4.8, Microsoft.Office.Interop.Excel v12.0.4518.1014, NUnit v3.12...)

But after upgrading to Moq 4.14 & Microsoft.Office.Interop.Excel v15.0.4795.1000, all the Mock.Of on interop classes fail to initialize.
I had other setup issues following interop upgrade (because they stated to use `dynamic` in some places, instead of `object`) but it seems that this is a different problem.

```
[Test]
public void Test_Works()
{
                var worksheet = new Mock<Worksheet>();
                var parent = new Mock<Workbook>();
                parent.Setup(x => x.FullName).Returns(string.Empty);
                worksheet.Setup(x => x.Parent).Returns(parent.Object);
}

[Test]
public void Test_Fails()
{
                var worksheet = new Mock<Worksheet>();
                var v = Mock.Of<Workbook>(x => x.FullName == string.Empty);
                worksheet.Setup(x => x.Parent).Returns(v);
}
```

The error is different based on the Moq version, but they all fail : 

Moq v4.13.1

```
  Message:
    System.ArgumentException : Method 'Moq.Language.Flow.IReturnsResult`1[Microsoft.Office.Interop.Excel.Workbook] Returns(System.String)' declared on type 'Moq.Language.IReturns`2[Microsoft.Office.Interop.Excel.Workbook,System.String]' cannot be called with instance of type 'Moq.Mock`1[System.String]'
  Stack Trace:
    Expression.ValidateCallInstanceType(Type instanceType, MethodInfo method)
    Expression.ValidateStaticOrInstanceMethod(Expression instance, MethodInfo method)
    Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
    MockSetupsBuilder.ConvertToSetup(Expression targetObject, Expression left, Expression right)
    MockSetupsBuilder.ConvertToSetupProperty(Expression targetObject, Expression left, Expression right)
    MockSetupsBuilder.ConvertToSetup(Expression left, Expression right)
    MockSetupsBuilder.VisitBinary(BinaryExpression node)
    BinaryExpression.Accept(ExpressionVisitor visitor)
    ExpressionVisitor.Visit(Expression node)
    ExpressionVisitor.VisitLambda[T](Expression`1 node)
    Expression`1.Accept(ExpressionVisitor visitor)
    ExpressionVisitor.Visit(Expression node)
    ExpressionVisitor.VisitUnary(UnaryExpression node)
    MockSetupsBuilder.VisitUnary(UnaryExpression node)
    UnaryExpression.Accept(ExpressionVisitor visitor)
    ExpressionVisitor.Visit(Expression node)
    ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
    ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
    MockSetupsBuilder.VisitMethodCall(MethodCallExpression node)
    MethodCallExpression.Accept(ExpressionVisitor visitor)
    ExpressionVisitor.Visit(Expression node)
    MockQueryable`1.Execute[TResult](Expression expression)
    Queryable.First[TSource](IQueryable`1 source, Expression`1 predicate)
    Mock.Of[T](Expression`1 predicate, MockBehavior behavior)
    Mock.Of[T](Expression`1 predicate)
    UnitTest1.Test_Fails() line 22
```

Moq v4.14.6

```
  Message:
    System.ArgumentException : Object instance was not created by Moq.
    Parameter name: mocked
  Stack Trace:
    Mock.Get[T](T mocked)
    lambda_method(Closure , Workbook )
    Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
    lambda_method(Closure )
    EnumerableExecutor`1.Execute()
    IQueryProvider.Execute[S](Expression expression)
    Queryable.First[TSource](IQueryable`1 source, Expression`1 predicate)
    lambda_method(Closure )
    MockQueryable`1.Execute[TResult](Expression expression)
    Queryable.First[TSource](IQueryable`1 source, Expression`1 predicate)
    Mock.Of[T](Expression`1 predicate, MockBehavior behavior)
    Mock.Of[T](Expression`1 predicate)
    UnitTest1.Test_Fails() line 22
```

Is it expected?
Do you have a workaround ?
Replacing all the `Mock.Of<>()` by `new Mock<>() + setup` is a lot of work and adds a lot of "useless" rows...


Hints:
> But after upgrading to Moq 4.14 & Microsoft.Office.Interop.Excel v15.0.4795.1000

@killergege, ~~can you tell which of the two updates causes the issue?~~ That must be due to the interop update, because I can reproduce the problem you're seeing with all versions of Moq, going all the way back to 4.0.0. I cannot currently offer a workaround other than the one you're already aware of&mdash;using `new Mock<T>` instead&mdash;but let's keep that around as something we should fix.

Unfortunately I don't have a copy of the Excel v12 type library available. Could you possibly provide an assembly containing the above two tests, compiled against the old versions where everything passed? If you could ensure that the COM interfaces get embedded in the assembly, that would allow me to investigate what changes in the type libraries make Moq fail.
We are using the nuget package to get the interop objects : https://www.nuget.org/packages/microsoft.office.interop.excel/
Our build server don't have Excel so I guess this is enough. If not I'll try to get you what you asked but in my corporate network this is not easy to send files outside :).
Ah, excellent! I wasn't aware of that NuGet package. Thank you for pointing me towards it... that should indeed be sufficient.
@killergege, I've compared the Excel COM interop libraries. They appear to differ in (among other things) the presence or absence of `[CompilerGenerated]` custom attributes on the `Workbook` and related types, and it appears to be the presence of that attribute that causes stuff inside Moq to go wrong:

Moq queries for this attribute in some places to recognize captured variables and anonymous types (those can show up in LINQ queries that use query comprehension syntax). In the current version, Moq erroneously enters into [this "then" clause](https://github.com/moq/moq4/blob/5b8fbc477a67fc36415e3114cb4a4a4492a91e4c/src/Moq/Linq/MockSetupsBuilder.cs#L155) when it shouldn't... and that's what makes your code fail.

Not yet sure how to fix this properly, but at least a fix is possible. A very crude fix would consist of adding an `&& !pe.Type.IsImport` check in the line of code referenced above... but that would be hardly pretty. I'll try to come up with something better.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a29c291e..e5124dc4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
 * Setup not triggered due to VB.NET transparently inserting superfluous type conversions into a setup expression (@InteXX, #1067)
 * Nested mocks created by `Mock.Of<T>()` no longer have their properties stubbed since version 4.14.0 (@vruss, @1071)
 * `Verify` fails for recursive setups not explicitly marked as `Verifiable` (@killergege, #1073)
+* `Mock.Of<>` fails for COM interop types that are annotated with a `[CompilerGenerated]` custom attribute (@killergege, #1072)
 
 
 ## 4.14.6 (2020-09-30)
diff --git a/src/Moq/Linq/MockSetupsBuilder.cs b/src/Moq/Linq/MockSetupsBuilder.cs
index bf02c5bf3..328c5010f 100644
--- a/src/Moq/Linq/MockSetupsBuilder.cs
+++ b/src/Moq/Linq/MockSetupsBuilder.cs
@@ -151,7 +151,7 @@ private sealed class ReplaceMockObjectWithParameter : ExpressionVisitor
 
 			protected override Expression VisitMember(MemberExpression node)
 			{
-				if (node.Expression is ParameterExpression pe && pe.Type.IsDefined(typeof(CompilerGeneratedAttribute)))
+				if (node.Expression is ParameterExpression pe && pe.Type.IsDefined(typeof(CompilerGeneratedAttribute)) && pe.Type.Name.Contains("f__AnonymousType"))
 				{
 					// In LINQ query expressions with more than one `from` clause such as:
 					//


Test patch:
diff --git a/tests/Moq.Tests/ComCompatibilityFixture.cs b/tests/Moq.Tests/ComCompatibilityFixture.cs
index a6f07a6b7..43234ccc0 100644
--- a/tests/Moq.Tests/ComCompatibilityFixture.cs
+++ b/tests/Moq.Tests/ComCompatibilityFixture.cs
@@ -1,6 +1,9 @@
 // Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors.
 // All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.
 
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
 using Moq.Tests.ComTypes;
 
 using Xunit;
@@ -100,5 +103,50 @@ public void COM_interop_type_indexer_setter_is_recognized_as_such()
 			var setter = indexer.GetSetMethod(true);
 			Assert.True(setter.IsSetAccessor() && setter.IsIndexerAccessor());
 		}
+
+		[Fact]
+		public void Can_create_mock_of_Excel_Workbook_using_Mock_Of_1()
+		{
+			_ = Mock.Of<GoodWorkbook>(workbook => workbook.FullName == "");
+		}
+
+		[Fact]
+		public void Can_create_mock_of_Excel_Workbook_using_Mock_Of_2()
+		{
+			_ = Mock.Of<BadWorkbook>(workbook => workbook.FullName == "");
+		}
+
+		// The following two interfaces are simplified versions of the `_Workbook` interface from
+		// two different versions of the `Microsoft.Office.Excel.Interop` interop assemblies.
+		// Note how they differ only in one `[CompilerGenerated]` attribute.
+
+		[ComImport]
+		[Guid("000208DA-0000-0000-C000-000000000046")]
+		public interface GoodWorkbook
+		{
+			[DispId(289)]
+			string FullName
+			{
+				[DispId(289)]
+				[LCIDConversion(0)]
+				[return: MarshalAs(UnmanagedType.BStr)]
+				get;
+			}
+		}
+
+		[ComImport]
+		[CompilerGenerated]
+		[Guid("000208DA-0000-0000-C000-000000000046")]
+		public interface BadWorkbook
+		{
+			[DispId(289)]
+			string FullName
+			{
+				[DispId(289)]
+				[LCIDConversion(0)]
+				[return: MarshalAs(UnmanagedType.BStr)]
+				get;
+			}
+		}
 	}
 }