如何在c#中使用moq编写一个将接口属性映射到键值对的通用模拟
本文关键字:映射 属性 一个 接口 模拟 键值对 moq | 更新日期: 2023-09-27 18:04:21
我想写一个可以为任何接口创建mock的方法。
public T GetMock<T>(IDictionary<string, object> data) where T : class
我首先只关心属性getter。所有getter都应该返回存储在字典中的值。属性名称是此字典中的一个键。下面的代码说明了预期的用法:
public interface IFoo
{
string Property1 { get; }
int Property2 { get; }
DateTime Property3 { get; }
}
[Test]
public void TestY()
{
var data = new Dictionary<string, object>
{
{"Property1", "Hello"},
{"Property2", 5},
{"Property3", DateTime.Today}
};
var mock = GetMock<IFoo>(data);
Assert.AreEqual("Hello", mock.Property1);
Assert.AreEqual(5, mock.Property2);
Assert.AreEqual(DateTime.Today, mock.Property3);
}
关键是我想模拟任何接口。因此,我的通用模拟创建看起来像:
public T GetMock<T>(IDictionary<string, object> data) where T : class
{
var mock = new Mock<T>();
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
var attributeName = property.Name;
var parameter = Expression.Parameter(type);
var body = Expression.Property(parameter, attributeName);
var lambdaExpression = Expression.Lambda<Func<T, object>>(body, parameter);
Func<object> getter = () => data[attributeName];
mock.Setup(lambdaExpression).Returns(getter);
}
return mock.Object;
}
应该可以工作,但是类型转换有一个问题。测试失败,显示消息:
系统。ArgumentException:系统类型的表达式。Int32'不能用于返回类型"系统"。对象'
我想我错过了一些转换lambda。有什么建议来解决这个问题吗?
猜测唯一的选择是使用Reflection,因为当前版本是4.2,但是仍然没有"Mock"。如Patrick所说,Setup(Expression expr)"实现。下面是我的示例:
public static class ConfigFactory<T> where T : class {
static T cachedImplInstance;
public static T BuildConfigGroupWithReflection() {
if (cachedImplInstance == null) {
Type interfaceType = typeof(T);
MethodInfo setupGetMethodInfo = typeof(Mock<T>).GetMethod("SetupGet");
Mock<T> interfaceMock = new Mock<T>();
IDictionary<Type, MethodInfo> genericSetupGetMethodInfos = new Dictionary<Type, MethodInfo>();
IDictionary<Type, MethodInfo> specificReturnsMethodInfos = new Dictionary<Type, MethodInfo>();
if (setupGetMethodInfo != null)
foreach (PropertyInfo interfaceProperty in interfaceType.GetProperties()) {
string propertyName = interfaceProperty.Name;
Type propertyType = interfaceProperty.PropertyType;
ParameterExpression parameter = Expression.Parameter(interfaceType);
MemberExpression body = Expression.Property(parameter, propertyName);
var lambdaExpression = Expression.Lambda(body, parameter);
MethodInfo specificSetupGetMethodInfo =
genericSetupGetMethodInfos.ContainsKey(propertyType) ?
genericSetupGetMethodInfos[propertyType] :
genericSetupGetMethodInfos[propertyType] = setupGetMethodInfo.MakeGenericMethod(propertyType);
object setupResult = specificSetupGetMethodInfo.Invoke(interfaceMock, new[] { lambdaExpression });
MethodInfo returnsMethodInfo =
specificReturnsMethodInfos.ContainsKey(propertyType) ?
specificReturnsMethodInfos[propertyType] :
specificReturnsMethodInfos[propertyType] = setupResult.GetType().GetMethod("Returns", new[] { propertyType });
if (returnsMethodInfo != null)
returnsMethodInfo.Invoke(setupResult, new[] { Settings.Default[propertyName] });
}
cachedImplInstance = interfaceMock.Object;
}
return cachedImplInstance;
}
}
注意" returnnsmethodinfo。调用(setupResult, new[]{设置。默认[propertyName]});"-你可以把你的字典放在这里。
比如说,我们有接口:
public interface IConfig {
string StrVal { get; }
int IntVal { get; }
StringCollection StrsVal { get; }
string DbConnectionStr { get; }
string WebSvcUrl { get; }
}
然后,使用方法如下(假设我们的项目有相应的名称/类型/值的"设置"):
IConfig cfg0 = ConfigFactory<IConfig>.BuildConfigGroupWithReflection();
这是一半的答案,因为我在Moq中没有看到任何支持这样做。要获得正确的Func
,请执行以下操作:
// In your for loop from above...
var attributeName = property.Name;
var parameter = Expression.Parameter(type);
var body = Expression.Property(parameter, attributeName);
// Add this line to create the correct Func type
var func = typeof(Func<,>).MakeGenericType(typeof(T), property.PropertyType);
// Then use this Func to create the lambda
var lambdaExpression = Expression.Lambda(func, body, parameter);
问题是Setup
没有允许您传入表示Func
的非泛型表达式的过载。换句话说,这不会编译:
// Error: cannot convert from 'System.Linq.Expressions.LambdaExpression'
// to 'System.Linq.Expressions.Expression<System.Action<T>>'
mock.Setup(lambdaExpression);
所以在这一点上你卡住了。
你可以提交一个问题(或拉请求)到Moq项目,虽然我不知道这个应用程序是否有足够广泛的受众…