2011-06-23 83 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 >' –