Dynamically calling Moq Setup() at runtime

泄露秘密 提交于 2019-12-05 08:14:58

What you are looking for are Linq Expressions. Here is a sample of building a property accessory expression in action.

Using this class

public class ExampleClass
{
   public virtual string ExampleProperty
   {
      get;
      set;
   }

   public virtual List<object> ExampleListProperty
   {
      get;
      set;
   }
}

The following tests demonstrate access it's properties dynamically using the Linq.Expression classes.

[TestClass]
public class UnitTest1
{
   [TestMethod]
   public void SetupDynamicStringProperty()
   {
      var dynamicMock = new Mock<ExampleClass>();

      //Class type
      var parameter = Expression.Parameter( typeof( ExampleClass ) );           

      //String rep of property
      var body = Expression.PropertyOrField( parameter, "ExampleProperty" ); 

      //build the lambda for the setup method
      var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>( body, parameter );

      dynamicMock.Setup( lambdaExpression ).Returns( "Works!" );

      Assert.AreEqual( "Works!", dynamicMock.Object.ExampleProperty );
   }

   [TestMethod]
   public void SetupDynamicListProperty_IntFirstInList()
   {
      var dynamicMock = new Mock<ExampleClass>();

      var parameter = Expression.Parameter( typeof( ExampleClass ) );
      var body = Expression.PropertyOrField( parameter, "ExampleListProperty" );
      var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>( body, parameter );

      var listOfItems = new List<object> { 1, "two", DateTime.MinValue };
      dynamicMock.Setup( lambdaExpression ).Returns( listOfItems );

      Assert.AreEqual( typeof( int ), dynamicMock.Object.ExampleListProperty[0].GetType() );
      Assert.AreEqual( 1, dynamicMock.Object.ExampleListProperty[0] );

      Assert.AreEqual( 3, dynamicMock.Object.ExampleListProperty.Count );
   }

   [TestMethod]
   public void SetupDynamicListProperty_StringSecondInList()
   {
      var dynamicMock = new Mock<ExampleClass>();

      var parameter = Expression.Parameter( typeof( ExampleClass ) );
      var body = Expression.PropertyOrField( parameter, "ExampleListProperty" );
      var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>( body, parameter );

      var listOfItems = new List<object> { 1, "two" };
      dynamicMock.Setup( lambdaExpression ).Returns( listOfItems );

      Assert.AreEqual( typeof( string ), dynamicMock.Object.ExampleListProperty[1].GetType() );
      Assert.AreEqual( "two", dynamicMock.Object.ExampleListProperty[1] );

      Assert.AreEqual( 2, dynamicMock.Object.ExampleListProperty.Count );
   }
}

EDIT

You are taking a step too far with this code. This code is creating a method with the signature of the lambda you want and then it's executing it (.Invoke). You are then trying to pass the result of the object (hence the compile error) into the setup for Moq. Moq will do the method execution and hookup for you once you tell it how to act (hence the lambda). If you use the lambda expression creation that I provided, it will build what you need.

var funcType = typeof (Func<>).MakeGenericType(new Type[] {TableType, typeof(object)});

var lambdaMethod = typeof (Expression).GetMethod("Lambda");
var lambdaGenericMethod = lambdaMethod.MakeGenericMethod(funcType);
var lambdaExpression = lambdaGenericMethod.Invoke(body, parameter);

//var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>(body, parameter); // FOR REFERENCE FROM BRIAN'S CODE
instance.Setup(lambdaExpression).Returns(inMemoryTable);

Do this instead

var parameter = Expression.Parameter( TableType );
var body = Expression.PropertyOrField( parameter, "PutYourPropertyHere" );
var lambdaExpression = Expression.Lambda<Func<ExampleClass, object>>( body, parameter );

instance.Setup(lambdaExpression).Returns(inMemoryTable);

EDIT

Took a stab at correcting the GetMockContext. Please note the few changes (I marked each line). I think this is closer. I am wondering, does InMemoryTable inherit from DataContext? If not, the method signature will be incorrect.

public static object GetMockContext<T>() where T: DataContext
{
    Type contextType = typeof (T);
    var instance = new Mock<T>();  //Updated this line
    var propertyInfos = contextType.GetProperties();
    foreach (var table in propertyInfos)
    {
        //I'm only worried about ITable<> now, otherwise skip it
        if ((!table.PropertyType.IsGenericType) ||
            table.PropertyType.GetGenericTypeDefinition() != typeof(ITable<>)) continue;

        //Determine the generic type of the ITable<>
        var TableType = GetTableType(table);
        //Create a List<T> of that type 
        var emptyList = CreateGeneric(typeof(List<>), TableType);
        //Create my InMemoryTable<T> of that type
        var inMemoryTable = CreateGeneric(typeof(InMemoryTable<>), TableType, emptyList);

        //NOW SETUP MOCK TO RETURN THAT TABLE
        var parameter = Expression.Parameter(contextType);
        var body = Expression.PropertyOrField(parameter, table.Name);
        var lambdaExpression = Expression.Lambda<Func<T, object>>(body, parameter); 

        instance.Setup(lambdaExpression).Returns(inMemoryTable);
    }
    return instance.Object; //had to change the method signature because the inMemoryTable is not of type DataContext. Unless InMemoryTable inherits from DataContext?
}

I hope this helps!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!