在运行时动态调用Moq Setup()

时间:2011-06-23 19:03:27

标签: linq-to-sql reflection moq

我想创建一个工厂,为我的单元测试创​​建常用的模拟对象。我已经设法设置我的测试,所以我可以模拟Linq2Sql DataContext并返回内存表而不是命中数据库。我这样设置:

_contactsTable = new InMemoryTable<Contact>(new List<Contact>());
_contactEmailsTable = new InMemoryTable<ContactEmail>(new List<ContactEmail>());
//  repeat this for each table in the ContactsDataContext

var mockContext = new Mock<ContactsDataContext>();
mockContext.Setup(c => c.Contacts).Returns(_contactsTable);
mockContext.Setup(c => c.ContactEmails).Returns(_contactEmailsTable);
// repeat this for each table in the ContactsDataContext

如果DataContext包含很多表,这会很繁琐,所以我认为使用反射来从DataContext中获取所有表的简单工厂方法可能会有所帮助:

public static DataContext GetMockContext(Type contextType)
{
    var instance = new Mock<DataContext>();
    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
        //How do I call instance.Setup(i=>i.THEPROPERTYNAME).Returns(inMemoryTable) ??
    }
return instance.Object;
}

到目前为止,我已经想出了如何创建我需要为Mock设置的对象,但我无法弄清楚如何动态调用Moq的Setup()传递属性名称。我开始将反射调用到Invoke()Moq的Setup()方法,但它的速度非常快。

有没有人有一种简单的方法可以像这样动态调用Setup()和Returns()?

编辑:Brian的回答让我在那里。以下是它的工作原理:

public static DataContext GetMockContext<T>() where T: DataContext
    {
        Type contextType = typeof (T);
        var instance = new Mock<T>();
        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;
    }

1 个答案:

答案 0 :(得分:8)

您正在寻找的是Linq Expressions。以下是构建属性附件表达式的示例。

使用此课程

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

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

以下测试演示了使用Linq.Expression类动态访问它的属性。

[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 );
   }
}

修改

您正在使用此代码迈出一步。这段代码创建了一个带有你想要的lambda签名的方法,然后它正在执行它(.Invoke)。然后,您尝试将对象的结果(因此编译错误)传递给Moq的设置。一旦你告诉它如何行动(因此是lambda),Moq将为你执行方法执行和连接。如果您使用我提供的lambda表达式创建,它将构建您需要的内容。

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);

改为

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);

修改

尝试纠正GetMockContext。请注意一些变化(我标记了每一行)。我认为这更接近了。我想知道,InMemoryTable是否继承自DataContext?如果不是,方法签名将不正确。

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?
}

我希望这有帮助!