使用 Moq 的 Elasticsearch Nest 单元测试:无法模拟混凝土 class

Elasticsearch Nest unit tests with Moq : fail to mock concrete class

我需要在我的存储库中使用的 Nest.IElasticClient 接口中模拟一些方法和属性。 例如,我需要将 Indices.Exists() 方法存根到 return 具有 Exists 属性 return 的 ExistsResponse 为真。

问题是具体的 class 没有接口实现,在 Exists 属性 上也没有 setter,并且在 Nest 库中也没有声明为虚拟的:

public class ExistsResponse : ResponseBase
{
    public ExistsResponse();
    public bool Exists { get; }
}

public ExistsResponse Exists(Indices index, Func<IndexExistsDescriptor, IIndexExistsRequest> selector = null);

因此,对于模拟,我尝试在具体 class 上设置 属性,但以下所有方法都失败了,我不知道该怎么做 ...

/* Fail with exception :
System.NotSupportedException : Unsupported expression: x => x.Exists
    Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
    var mock1 = new Mock<ExistsResponse>();
    obj.SetupGet(f => f.Exists).Returns(true);

/* Fail with exception :
 System.NotSupportedException : Unsupported expression: f => f.Exists
    Non-overridable members (here: ExistsResponse.get_Exists) may not be used in setup / verification expressions.
*/
    var mock2 = Mock.Of<ExistsResponse>(x => x.Exists == true);

/* Fail with exception :
System.ArgumentException : Property set method not found.
*/
    var mock3 = new ExistsResponse();
    var property = typeof(ExistsResponse).GetProperty("Exists", BindingFlags.Public | BindingFlags.Instance);
    property.SetValue(mock3, true);

/* Fail with exception :
System.NullReferenceException (setter is null)
*/
    var mock4 = new ExistsResponse();
    var setter = property.GetSetMethod(true);
    setter.Invoke(mock4, new object[] { true });


    // My Mock on the Indices.Exists method
    var elasticMock = new Mock<IElasticClient>();
    elasticMock
    .Setup(x => x.Indices.Exists(It.IsAny<string>(), null))
    .Returns(/*** my stubbed object here ***/); // <== how to stub the concrete class to return a ExistsResponse.Exists = true ?

我的库:

Nest 7.12.1
Moq 4.15.2
XUnit 2.4.1
.Net 5

感谢您的帮助

模拟你不拥有的东西通常不好,但如果你想模拟弹性客户端响应,最好的方法是使用 InMemorryConnection,你可以像这样用 InMemorryConnection 实例化一个新的 elasticClient:

InMemory 弹性客户端:

var connection = new InMemoryConnection();
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection);
var client = new ElasticClient(settings);

然后您将需要伪造响应并要求客户 return 如下所示:

var response = new
{
    took = 1,
    timed_out = false,
    _shards = new
    {
        total = 2,
        successful = 2,
        failed = 0
    },
    hits = new
    {
        total = new { value = 25 },
        max_score = 1.0,
        hits = Enumerable.Range(1, 25).Select(i => (object)new
        {
            _index = "project",
            _type = "project",
            _id = $"Project {i}",
            _score = 1.0,
            _source = new { name = $"Project {i}" }
        }).ToArray()
    }
};

var responseBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(response));
var connection = new InMemoryConnection(responseBytes, 200); 
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool, connection).DefaultIndex("project");
var client = new ElasticClient(settings);

var searchResponse = client.Search<Project>(s => s.MatchAll());

您可以找到有关 InMemoryConnection 的更多信息here

如果您想正确遵循不要嘲笑您不拥有的东西,您需要执行以下步骤:

  1. 包装ElasticClient和所有RequestResponses 的 ElasticClient 在这个库中,然后你可以轻松地模拟你需要的东西。
  2. 您应该使用集成测试来测试您的包装器 class,以确保您的包装器 class 按预期工作。

更新:

为了模拟 Indices.Exists 你只需要发送 200 OK 所以你只需要这样做:

var connection = new InMemoryConnection(responseBytes, 200);