2012-05-23 74 views
24

使用Moq创建数据集的模拟。使用反射创建表达式<Func<,>>使用反射

我已经创建了一个小辅助类,它允许我有一个内存存储而不是一个使单元测试轻而易举的数据库。这样我可以添加和删除我的模拟数据集中的项目,这使我可以测试我的插入和删除服务调用。

在模拟的设置我有一条线,看起来像下面

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

我假装有很多的属性,所以我想执行使用反射此设置步骤。我已经设法通过反射工作的Returns部分工作,但我坚持在lambda方法Setup

Setup需要一个

Expression<Func<GoalsModelUnitOfWork, IQueryable<AcademicCycle>>>对应于i => i.AcademicCycles

,我想动态地创建这一点。使用反射我有以下几点:

属性的名称:“AcademicCycles”

类型IQueryable<AcademicCycle>

类型AcademicCycle

我也有i的实例在lambda声明这是一个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.Lambda的返回类型的输入类型很弱,但Expression.Lambda确实会构造适当的“Expression >”类型的实例。 – itowlson

+1

另外我不认为你需要中间两行。从更简单的情况下测试,'var lambda = Expression.Lambda(parameter,property)'应该可以工作(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

该方法应该构造lambda表达式。由于您正在通过反射调用Setup方法,因此不需要强类型的lambda表达式;你打算把它作为一个对象数组的一部分,当你调用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; 
    }