如何在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。有什么建议来解决这个问题吗?

如何在c#中使用moq编写一个将接口属性映射到键值对的通用模拟

猜测唯一的选择是使用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项目,虽然我不知道这个应用程序是否有足够广泛的受众…