【问题标题】:How to write a generic mock which maps interface properties to key-value pairs in c# using moq如何使用moq编写一个通用模拟,将接口属性映射到c#中的键值对
【发布时间】:2014-05-21 14:13:56
【问题描述】:

我想编写一个为任何接口创建模拟的方法。

public T GetMock<T>(IDictionary<string, object> data) where T : class

我首先只关心属性获取器。所有 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;
    }

它应该可以工作,但类型转换存在问题。测试失败并显示一条消息:

System.ArgumentException : 'System.Int32' 类型的表达式不能 用于返回类型'System.Object'

我想我错过了一些转换 lambda。有什么建议可以解决这个问题吗?

【问题讨论】:

  • 哪一行给了你这个例外? var lambdaExpression... ?
  • 没错。行var lambdaExpression = Expression.Lambda&lt;Func&lt;T, object&gt;&gt;(body, parameter);
  • 我希望这是因为您试图从Func&lt;T, object&gt; 返回一个(例如)int 参数,但很抱歉我不知道如何解决它:p跨度>
  • 这也是我的猜测。

标签: c# .net generics lambda moq


【解决方案1】:

猜猜唯一的选择是使用反射,因为当前版本是 4.2,但仍然没有“Mock.Setup(Expression expr)”实现,正如 Patrick 所说。 所以,这是我的示例:

    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;
    }
}

注意行“returnsMethodInfo.Invoke(setupResult, new[] { Settings.Default[propertyName] });” - 你可以把你的字典放在这里。

说,我们有接口:

public interface IConfig {
    string StrVal { get; }
    int IntVal { get; }

    StringCollection StrsVal { get; }

    string DbConnectionStr { get; }

    string WebSvcUrl { get; }
}

那么,用法如下(假设我们有我们项目的“设置”以及相应的名称/类型/值):

IConfig cfg0 = ConfigFactory<IConfig>.BuildConfigGroupWithReflection();

【讨论】:

    【解决方案2】:

    这是一个一半的答案,因为我在 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 project 提交问题(或拉取请求),但我不知道此应用程序是否有足够广泛的受众...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-19
      • 1970-01-01
      • 1970-01-01
      • 2011-11-13
      • 1970-01-01
      • 2019-10-13
      • 2011-09-03
      • 2014-07-12
      相关资源
      最近更新 更多