如何模拟Elasticsearch NEST的IGetMappingResponse进行单元测试

时间:2018-11-27 17:59:58

标签: c# unit-testing elasticsearch nest

我本人和我的大学已经建立了一个数据访问模块,该模块可以对ElasticeSearch(6.2.0)NoSQL数据库进行CRUD和搜索操作。我们使用的是高级客户端NEST(6.2.0)映射到Elasticsearch查询DSL。该模块已在目标框架.NET Standard 2.0中的类库中用C#编码。

在某些方法中,有必要检索存储在ElasticSearch中的索引的索引映射,以便可以使用诸如字段类型和文本属性之类的字段信息来构建对Elasticsearch数据库的搜索查询。我们可以使用Nest的GetMapping方法(返回Nest.IGetMappingResponse)来做到这一点。

我们在执行代码方面没有问题。一切正常。但是,当我们为使用GetMapping方法的方法构建单元测试时,我们无法模拟其返回的响应(IGetMappingResponse)。我最初使用FakeItEasy进行伪造,然后在对象中插入了我需要的一些数据。但是,为了更好地解释我的问题,我创建了一个实现IGetMappingResponse的类来模拟我的响应。当我尝试为IndexMapping的Mapping属性创建 TypeMappings 的实例时出现问题(作为回报,该属性用于模拟的Indices)。我收到一条错误消息,说“ TypMappings”不包含带有0个参数的构造函数。

public class MockedGetMappingResponse : IGetMappingResponse
    {
        public IReadOnlyDictionary<IndexName, IndexMappings> Indices
        {
            get
            {
                return new ReadOnlyDictionary<Nest.IndexName, Nest.IndexMappings>(new Dictionary<Nest.IndexName, Nest.IndexMappings>
                {
                    ["statuses"] = new IndexMappings
                    {
                        Mappings = new TypeMappings() //Error happens here!!
                    }                      

                });
            }
            set { }
        }

        public IReadOnlyDictionary<IndexName, IndexMappings> Mappings
        {
            get
            {
                return null;
            }
            set { }
        }

        public void Accept(IMappingVisitor visitor)
        {
            // Just a test
        }

        public bool IsValid
        {
            get
            {
                return true;
            }
        }

        public ServerError ServerError
        {
            get
            {
                return null;
            }
        }

        public Exception OriginalException
        {
            get
            {
                return null;
            }
        }

        public string DebugInformation
        {
            get
            {
                return "";
            }
        }

        public IApiCallDetails ApiCall
        {
            get
            {
                return null;
            }
            set
            {
                //do nothing
            }
        }

        public bool TryGetServerErrorReason(out string reason)
        {
            reason = "";
            return false;
        }
    }      

}

当我查询Nest类型 TypeMappings 的定义时,没有看到构建的构造函数。因此,我假设它应该使用默认值,即没有参数的构造函数。但显然不是。我需要知道如何在IGetMappingResponse中模拟TypeMappings。如果无法创建TypeMappings实例,则我需要知道如何为预期的响应创建模拟的IGetMappingResponse,以便测试代码。

1 个答案:

答案 0 :(得分:1)

在Nkosi的帮助下,我发现 TypeMappings 具有内部构造函数。知道了这一点,我使用了反射创建了TypeMappings的实例。这是我用来创建对象的代码。

IReadOnlyDictionary<TypeName, TypeMapping> backingDictionary = new ReadOnlyDictionary<TypeName, TypeMapping>(new Dictionary<TypeName, TypeMapping>
        {
            [typeName.Name] = typeMapping

        });           

        Type[] typeMappingsArgs = new Type[] { typeof(IConnectionConfigurationValues), typeof(IReadOnlyDictionary<TypeName, TypeMapping>) };
        object[] typeMappingsInputParams = new object[] { elasticClient.ConnectionSettings, backingDictionary };
        TypeMappings typeMappings = (TypeMappings)typeof(TypeMappings).GetConstructor(
              BindingFlags.NonPublic | BindingFlags.Instance,
              null, typeMappingsArgs, null).Invoke(typeMappingsInputParams);

这是用于创建模拟的IGetMappingResponse的代码。我用FakeItEasy插入了一些信息。

private Nest.IGetMappingResponse GetFakeMappingResponse(Nest.IElasticClient elasticClient)
    {
        var fieldName = "fieldName";
        var indexName = "indexName";
        var documentTypeName = "documentTypeName";

        var typeMapping = new TypeMapping();
        var properties = new Properties();
        typeMapping.Properties = properties;

        var property = new TextProperty();
        property.Name = fieldName;            

        PropertyName propertyName = new PropertyName(fieldName);

        typeMapping.Properties.Add(propertyName, property);

        Type[] typeNameArgs = new Type[] { typeof(string) };
        object[] typeNameInputParams = new object[] { documentTypeName };
        TypeName typeName = (TypeName)typeof(TypeName).GetConstructor(
              BindingFlags.NonPublic | BindingFlags.Instance,
              null, typeNameArgs, null).Invoke(typeNameInputParams);


        IReadOnlyDictionary<TypeName, TypeMapping> backingDictionary = new ReadOnlyDictionary<TypeName, TypeMapping>(new Dictionary<TypeName, TypeMapping>
        {
            [typeName.Name] = typeMapping

        });           

        Type[] typeMappingsArgs = new Type[] { typeof(IConnectionConfigurationValues), typeof(IReadOnlyDictionary<TypeName, TypeMapping>) };
        object[] typeMappingsInputParams = new object[] { elasticClient.ConnectionSettings, backingDictionary };
        TypeMappings typeMappings = (TypeMappings)typeof(TypeMappings).GetConstructor(
              BindingFlags.NonPublic | BindingFlags.Instance,
              null, typeMappingsArgs, null).Invoke(typeMappingsInputParams);

        IndexMappings indexMappings = new IndexMappings();
        typeof(IndexMappings).GetProperty("Mappings", BindingFlags.Public | BindingFlags.Instance).SetValue(indexMappings, typeMappings);

        ReadOnlyDictionary<Nest.IndexName, Nest.IndexMappings> indices = new ReadOnlyDictionary<Nest.IndexName, Nest.IndexMappings>(new Dictionary<Nest.IndexName, Nest.IndexMappings>
        {
            [indexName] = indexMappings

        });

        var fakeMappingResponse = A.Fake<IGetMappingResponse>();
        A.CallTo(() => fakeMappingResponse.ServerError).Returns(null);
        A.CallTo(() => fakeMappingResponse.IsValid).Returns(true);
        A.CallTo(() => fakeMappingResponse.Indices).Returns(indices);

        return fakeMappingResponse;
    }