Search code examples
elasticsearchnest

Using InMemoryConnection in NEST with multiple calls to Elasticsearch


I am moving from mocking calls to Elasticsearch (through NEST) to using the InMemoryConnection class and some hard-coded JSON to simulate a real Elasticsearch answering the requests made from C# code. Works great and much better than mocks, which require deep understanding and knowledge about how NEST is implemented and breaks very easily.

My question is, if I want to simulate multiple calls to Elasticsearch, I don't see any easy way to do this. InMemoryConnection accepts a single JSON string only:

var connection = new InMemoryConnection(Encoding.UTF8.GetBytes(json));

It would be nice to say something like, return x on the first call and y on the second.

Similar issue is when verifying calls to Elasticsearch. I can use OnRequestCompleted to set a couple of booleans like this:

OnRequestCompleted(response =>
{
    if (response.Uri.ToString().Contains("/blabla/_search")) {}
    else if (response.HttpMethod == HttpMethod.POST && response.Success && response.Uri.ToString().StartsWith("http://localhost:9200/blabla"))
        wasCalled = true;
    else // We don't expect any other calls
        failed = true;
})

When asserting on those booleans. But that doesn't feel right.

Any ideas would be appreciated.


Solution

  • InMemoryConnection is geared more towards an individual request/response as opposed to stubbing multiple different requests/responses, however, an IConnection can be derived from it to achieve this purpose

    public class TestConnection : InMemoryConnection
    {
        private (byte[], int, string) DetermineResponse(RequestData requestData)
        {
            byte[] responseBody = null;
            int statusCode = 200;
            string contentType = RequestData.MimeType;
    
            // do the switching here e.g. ctor might take a dictionary of Predicate<RequestData> -> Responses
            if (requestData.Uri.AbsolutePath == "/_search")
            {
                responseBody = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { }));
            }
            
            return (responseBody, statusCode, contentType);
        }
            
        public override TResponse Request<TResponse>(RequestData requestData)
        {
            var (responseBody, statusCode, contentType) = DetermineResponse(requestData);   
            return base.ReturnConnectionStatus<TResponse>(requestData, responseBody, statusCode, contentType);
        }
    
        public override Task<TResponse> RequestAsync<TResponse>(RequestData requestData, CancellationToken cancellationToken)
        {
            var (responseBody, statusCode, contentType) = DetermineResponse(requestData);   
            return base.ReturnConnectionStatusAsync<TResponse>(requestData, cancellationToken, responseBody, statusCode, contentType);
        }
    }
    

    Something like this would probably be a great addition to Elasticsearch.Net.VirtualizedCluster.

    Similar issue is when verifying calls to Elasticsearch. I can use OnRequestCompleted to set a couple of booleans like this:

    OnRequestCompleted(response => { if (response.Uri.ToString().Contains("/blabla/_search")) {} else if (response.HttpMethod == HttpMethod.POST && response.Success && response.Uri.ToString().StartsWith("http://localhost:9200/blabla")) wasCalled = true; else // We don't expect any other calls failed = true; })

    Rather than asserting these in OnRequestCompleted, I'd either

    1. Record the calls in TestConnection and assert they were called there. I wonder if what might be being asserted in this manner could be deduced from the response instead somehow, though.

    or

    1. Make the assertions on response.ApiCall