Динамический вызов Moq Setup () во время выполнения - PullRequest
5 голосов
/ 23 июня 2011

Я хочу создать фабрику, которая будет создавать общеизвестные объекты для моих модульных тестов. Мне уже удалось настроить свои тесты, чтобы я мог смоделировать 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's 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;
    }

1 Ответ

8 голосов
/ 30 июля 2011

То, что вы ищете, это выражения 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 );
   }
}

РЕДАКТИРОВАТЬ

Вы слишком далеко продвинулись с этим кодом.Этот код создает метод с нужной сигнатурой лямбды, а затем выполняет ее (.Invoke).Затем вы пытаетесь передать результат объекта (отсюда и ошибку компиляции) в настройку для Moq.Moq выполнит выполнение метода и подключит его, как только вы скажете ему, как действовать (отсюда и лямбда).Если вы используете созданное мной лямбда-выражение, оно создаст то, что вам нужно.

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

Надеюсь, это поможет!

...