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

标签 c# reflection linq-to-sql 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()?

编辑:布莱恩的回答让我明白了。其工作原理如下:

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

最佳答案

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

使用此类:

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 的设置中。一旦您告诉 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?
}

希望这会有所帮助!

关于c# - 在运行时动态调用 Moq Setup(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6459307/

相关文章:

java - 指定可以通过反射获取对象的哪些字段

c# - LINQ to Entities 仅支持无参数构造函数和初始值设定项

c# - 如何有效地转换 List<T>?

c# - Entity Framework /Linq to SQL : Skip & Take

c# - 是否有 IDictionary 实现在缺少键时返回默认值而不是抛出?

linq-to-sql - 为什么 LinqPad 创建字段而不是属性?

c# - 具有单独实例的 .NET 自定义线程池

java - 无法使用 Class.forName() 查找包中的类

c# - 通过点击手势导航到 xamarin 表单中的下一页

c# - 如何以编程方式固定默认动态磁贴?