我使用Moq创建数据集的模拟。
我创建了一个小助手类,它允许我有一个内存存储而不是一个使单元测试变得轻而易举的数据库。这样我就可以在模拟数据集中添加和删除项目,这样我就可以测试插入和删除服务调用。
在模拟设置期间,我有一行看起来如下
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
我在lambda语句中也有i
的实例是GoalsModelUnitOfWork
答案 0 :(得分:29)
动态创建表达式的代码如下:
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<...>>
执行类型转换以将其作为Func
传递您的设置函数的参数,因为您不知道Setup
的泛型类型参数。所以你必须通过反射来调用this.GetType().GetMethod("Setup", yourExpression.GetType()).Invoke(this, yourExpression);
方法:
{{1}}
答案 1 :(得分: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 :(得分: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;
}