2012-05-23 2 views
24

Im использование Moq для создания mocks набора данных.Создать выражение <Func<,>> используя отражение

Я создал небольшой вспомогательный класс, который позволяет мне иметь в памяти вместо базы данных, которая делает модульное тестирование бриз. Таким образом, я могу добавлять и удалять элементы из моего набора данных, это позволяет мне проверять мои вставки и удалять вызовы служб.

Во время установки Ложная У меня есть строка, которая выглядит следующим

this.Setup(i => i.AcademicCycles).Returns(mockStore.GetList<AcademicCycle>()); 

Мой издеваться имеет много свойств, так что я хотел бы выполнить этот шаг настройки с помощью отражения. Мне удалось сделать Returns часть процесса, работающего через отражение, но я застрял на методе лямбда до Setup.

Setup принимает

Expression<Func<GoalsModelUnitOfWork, IQueryable<AcademicCycle>>> что соответствует i => i.AcademicCycles

, и я хотел бы создать это динамически. С помощью отражения я следующее:

Название объекта: «AcademicCycles»

Тип IQueryable<AcademicCycle>

Тип AcademicCycle

У меня также есть экземпляр i в операторе лямбда который является GoalsModelUnitOfWork

ответ

23

Код для создания выражения динамически будет выглядеть так:

ParameterExpression parameter = Expression.Parameter(typeof (GoalsModelUnitOfWork), "i"); 
MemberExpression property = Expression.Property(parameter, "AcademicCycles"); 

var queryableType = typeof (IQueryable<>).MakeGenericType(typeof (AcademicCycle)); 
var delegateType = typeof (Func<,>).MakeGenericType(typeof (GoalsModelUnitOfWork), queryableType); 

var yourExpression = Expression.Lambda(delegateType, property, parameter); 

Результат будет иметь нужный тип, но проблема в том, что тип возвращаемого Expression.Lambda() является LambdaExpression и вы не можете выполнить приведение типа к Expression<Func<...>>, чтобы передать его в качестве параметра для вашей функции настройки, потому что вы не» t знать параметры типового типа для Func. Таким образом, вы должны вызвать метод Setup отражения, тоже:

this.GetType().GetMethod("Setup", yourExpression.GetType()).Invoke(this, yourExpression); 
+1

На самом деле результат Expression.Lambda может быть приведен к 'Expression >', если вы статически знать параметры и типы возвращаемых значений. Internal Expression.Lambda действительно создает экземпляр соответствующего типа 'Expression >', хотя тип возврата Expression.Lambda слабо типизирован. – itowlson

+1

Также я не думаю, что вам нужны две средние линии. Из теста в более простом случае следует работать 'var lambda = Expression.Lambda (параметр, свойство)' (Expression.Lambda разработает тип делегата из типов и свойств). Однако мой тестовый код немного отличался от вашего и использовал более простые типы, поэтому ваш пробег может меняться ...! – itowlson

2

я решил взять трещины на него, и в конечном итоге с этим богом ужасным куском кода.

Я не эксперт по размышлениям, и это всего лишь первая попытка получить что-то работающее. Мне было бы очень интересно, какие другие подходы у людей есть, или какая-либо из библиотек обертки может сделать это лучше.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 
using Moq; 
using Xunit; 

namespace MyExample 
{ 
    public class Tests 
    { 
     [Fact] 
     public void Test() 
     { 
      Dictionary<Type, object> data = new Dictionary<Type, object> 
      { 
       { typeof(IQueryable<Cycle>), new List<Cycle> { new Cycle { Name = "Test" } }.AsQueryable() }, 
       { typeof(IQueryable<Rider>), new List<Rider> { new Rider { Name = "1"}, new Rider { Name = "2" } }.AsQueryable() } 
      }; 

      var mock = new Mock<IDataContext>(); 
      var setup = mock.GetType().GetMethods().Single(d => d.Name == "Setup" && d.ContainsGenericParameters); 
      var param = Expression.Parameter(typeof(IDataContext), "i"); 
      foreach (var property in typeof(IDataContext).GetProperties(BindingFlags.Public | BindingFlags.Instance)) 
      { 
       // Build lambda 
       var ex = Expression.Lambda(Expression.MakeMemberAccess(param, property), param); 

       // Get generic version of the Setup method 
       var typedSetup = setup.MakeGenericMethod(property.PropertyType); 

       // Run the Setup method 
       var returnedSetup = typedSetup.Invoke(mock, new[] { ex }); 

       // Get generic version of IReturns interface 
       var iReturns = typedSetup.ReturnType.GetInterfaces().Single(d => d.Name.StartsWith("IReturns`")); 

       // Get the generic Returns method 
       var returns = iReturns.GetMethod("Returns", new Type[] { property.PropertyType }); 

       // Run the returns method passing in our data 
       returns.Invoke(returnedSetup, new[] { data[property.PropertyType] }); 
      } 

      Assert.Equal(1, mock.Object.Cycles.Count()); 
     } 
    } 

    public class Cycle 
    { 
     public string Name { get; set; } 
    } 

    public class Rider 
    { 
     public string Name { get; set; } 
    } 

    public interface IDataContext 
    { 
     IQueryable<Cycle> Cycles { get; set; } 

     IQueryable<Rider> Riders { get; set; } 
    } 
} 
2

Этот метод должен содержать выражение лямбда. Поскольку вы вызываете метод установки методом отражения, вам не требуется строго типизированное лямбда-выражение; вы собираетесь передать его как часть массива объектов при вызове Invoke:

public LambdaExpression PropertyGetLambda(string parameterName, Type parameterType, string propertyName, Type propertyType) 
    { 
     var parameter = Expression.Parameter(parameterType, parameterName); 
     var memberExpression = Expression.Property(parameter, propertyName); 
     var lambdaExpression = Expression.Lambda(memberExpression, parameter); 
     return lambdaExpression; 
    } 

Я не думаю, что вы на самом деле нужно имя параметра.Если я прав, что вы могли бы упростить немного:

public LambdaExpression PropertyGetLambda(Type parameterType, string propertyName, Type propertyType) 
    { 
     var parameter = Expression.Parameter(parameterType); 
     var memberExpression = Expression.Property(parameter, propertyName); 
     var lambdaExpression = Expression.Lambda(memberExpression, parameter); 
     return lambdaExpression; 
    } 
Смежные вопросы