在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
相关产品推荐
相关产品推荐

