2011-06-23 47 views
4

我想创建一个工厂,为我的单元测试创​​建通常使用的模拟对象。我已经设置了我的测试,所以我可以模拟一个Linq2Sql DataContext并返回一个内存表而不是命中数据库。我把它像这样:在运行时动态调用Moq Setup()

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

到目前为止,我已经想出了如何创建我需要为模拟设置的对象,但我无法弄清楚如何动态调用传递属性名称的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; 
    } 
+0

使用lambda方法的三条线正在制作一个方法,然后调用它。您需要在我的答案中使用示例创建lambda方法并传递给安装方法。 –

+0

感谢您的编辑。在您提到的具有特定上下文(ContactsDataContext)的第一部分代码中,您希望将其迁移到使用通用上下文(DataContext)。这样做的问题在于,您将DataContext的模拟与模板类上存在的属性混合在一起。我将用更多的代码更新我的答案。 –

+0

啊,我想你的评论为我排序。我需要在顶部创建一个模拟而不是模拟。有用! –

回答

6

你在找什么是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? 
} 

我希望这有助于!

+0

它让我更接近一些,但如果我所知道的只是反射的类型,我仍然在动态地创建表达式。看到我的编辑原来的问题。 –

+0

尝试使用像Func 这样的lambda somenthing的func。我认为这会让你走到你需要去的地方。假设.net 4.0 ... –

+0

然后我得到不能从'System.Linq.Expressions.Expression >'转换为'System.Linq.Expressions.Expression >' –