You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在NEST中使用InMemoryConnection实现多Elasticsearch请求模拟

解决方案:自定义InMemoryConnection实现多请求响应与合理的请求验证

我刚好也踩过这两个坑——用InMemoryConnection没法按顺序返回不同响应,还有用布尔值验证请求太粗糙。下面给你分享两个针对性的实用方案:

1. 实现顺序返回不同响应的自定义Connection

InMemoryConnection确实只支持单个预设响应,但我们可以继承它,自己维护一个响应队列,每次请求取出下一个响应返回。这样就能轻松实现“首次返回x,第二次返回y”的需求:

public class SequentialInMemoryConnection : InMemoryConnection
{
    private readonly Queue<byte[]> _responseQueue;

    public SequentialInMemoryConnection(params string[] responses)
    {
        _responseQueue = new Queue<byte[]>(
            responses.Select(r => Encoding.UTF8.GetBytes(r)));
    }

    public override Task<IApiCallDetails> RequestAsync<TResponse>(
        RequestData requestData, 
        CancellationToken cancellationToken = default)
    {
        if (_responseQueue.TryDequeue(out var responseBytes))
        {
            var response = CreateResponse(requestData, responseBytes, HttpStatusCode.OK);
            return Task.FromResult((IApiCallDetails)response);
        }

        // 如果队列空了,可返回自定义错误响应
        var errorResponse = CreateResponse(requestData, Array.Empty<byte>(), HttpStatusCode.BadRequest);
        return Task.FromResult((IApiCallDetails)errorResponse);
    }

    public override IApiCallDetails Request<TResponse>(RequestData requestData)
    {
        if (_responseQueue.TryDequeue(out var responseBytes))
        {
            return CreateResponse(requestData, responseBytes, HttpStatusCode.OK);
        }

        return CreateResponse(requestData, Array.Empty<byte>(), HttpStatusCode.BadRequest);
    }

    private ApiCallDetails CreateResponse(RequestData requestData, byte[] responseBytes, HttpStatusCode statusCode)
    {
        return new ApiCallDetails
        {
            HttpStatusCode = statusCode,
            ResponseBodyInBytes = responseBytes,
            RequestBodyInBytes = requestData.RequestBodyInBytes,
            Uri = requestData.Uri,
            HttpMethod = requestData.HttpMethod,
            Success = (int)statusCode >= 200 && (int)statusCode < 300
        };
    }
}

使用方式:

// 准备不同的响应JSON
var firstResponse = @"{""hits"": {""hits"": []}}";
var secondResponse = @"{""hits"": {""hits"": [{""_source"": {""id"": 1}}]}}";

// 初始化自定义Connection,按传入顺序返回响应
var connection = new SequentialInMemoryConnection(firstResponse, secondResponse);

var settings = new ConnectionSettings(connection)
    .DefaultIndex("blabla");

var client = new ElasticClient(settings);

// 第一次请求返回firstResponse
var firstSearch = await client.SearchAsync<MyDocument>();
// 第二次请求返回secondResponse
var secondSearch = await client.SearchAsync<MyDocument>();

2. 更可靠的请求验证方案:记录并断言请求详情

用布尔值验证太简陋了,我们可以包装Connection,把所有请求的关键信息(方法、Uri、请求体、响应状态等)都记录下来,之后直接对这些记录进行断言,灵活性和准确性都高很多:

public class RequestTrackingConnection : IConnection
{
    private readonly IConnection _innerConnection;
    public List<RequestRecord> RequestRecords { get; } = new List<RequestRecord>();

    public RequestTrackingConnection(IConnection innerConnection)
    {
        _innerConnection = innerConnection;
    }

    public async Task<IApiCallDetails> RequestAsync<TResponse>(
        RequestData requestData, 
        CancellationToken cancellationToken = default)
    {
        var apiCallDetails = await _innerConnection.RequestAsync<TResponse>(requestData, cancellationToken);
        TrackRequest(requestData, apiCallDetails);
        return apiCallDetails;
    }

    public IApiCallDetails Request<TResponse>(RequestData requestData)
    {
        var apiCallDetails = _innerConnection.Request<TResponse>(requestData);
        TrackRequest(requestData, apiCallDetails);
        return apiCallDetails;
    }

    private void TrackRequest(RequestData requestData, IApiCallDetails apiCallDetails)
    {
        RequestRecords.Add(new RequestRecord
        {
            HttpMethod = requestData.HttpMethod,
            Uri = requestData.Uri,
            RequestBody = requestData.RequestBodyInBytes != null 
                ? Encoding.UTF8.GetString(requestData.RequestBodyInBytes) 
                : null,
            ResponseStatusCode = apiCallDetails.HttpStatusCode,
            IsSuccess = apiCallDetails.Success
        });
    }
}

public class RequestRecord
{
    public HttpMethod HttpMethod { get; set; }
    public Uri Uri { get; set; }
    public string RequestBody { get; set; }
    public HttpStatusCode? ResponseStatusCode { get; set; }
    public bool IsSuccess { get; set; }
}

使用方式:

// 先初始化顺序响应的Connection
var sequentialConnection = new SequentialInMemoryConnection(firstResponse, secondResponse);
// 包装成带请求跟踪的Connection
var trackingConnection = new RequestTrackingConnection(sequentialConnection);

var settings = new ConnectionSettings(trackingConnection)
    .DefaultIndex("blabla");

var client = new ElasticClient(settings);

// 执行请求
await client.SearchAsync<MyDocument>();
await client.IndexAsync(new MyDocument { Id = 1 });

// 断言请求记录
// 验证总请求数
Assert.Equal(2, trackingConnection.RequestRecords.Count);

// 验证第一个请求是搜索
var firstRequest = trackingConnection.RequestRecords[0];
Assert.Equal(HttpMethod.GET, firstRequest.HttpMethod);
Assert.Contains("/blabla/_search", firstRequest.Uri.ToString());
Assert.True(firstRequest.IsSuccess);

// 验证第二个请求是索引
var secondRequest = trackingConnection.RequestRecords[1];
Assert.Equal(HttpMethod.POST, secondRequest.HttpMethod);
Assert.StartsWith("http://localhost:9200/blabla/_doc/", secondRequest.Uri.ToString());
Assert.Equal(HttpStatusCode.OK, secondRequest.ResponseStatusCode);

这种方式不仅能验证请求是否被调用,还能检查请求的具体内容、方法、响应状态等,比单纯的布尔值判断靠谱多了。

内容的提问来源于stack exchange,提问作者ThomasArdal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 19:42:50