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

本文关键字:Moq-Setup 调用 运行时 动态 | 更新日期: 2023-09-27 18:00:27

我想创建一个工厂,为我的单元测试创建常见的模拟对象。我已经成功地设置了测试,这样我就可以模拟Linq2SqlDataContext并返回内存中的表,而不是访问数据库。我这样设置:

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

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

您要查找的是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?
}

我希望这能有所帮助!