I am not sure ElasticSearch's .net low level client is commonly used yet, because NEST seems to be the "go to" approach to implement an Elasticsearch client in .net.
Anyway, among many other methods, the main IElasticLowLevelClient interface exposes a method to create indices :
ElasticsearchResponse<T> IndicesCreate<T>(
string index,
PostData<object> body,
Func<CreateIndexRequestParameters, CreateIndexRequestParameters> requestParameters = null
) where T : class;
As explained in this doc, you can pass a string instead of an instance of PostData<object> thanks to one of the handy PostData's implicit operators :
public class PostData<T> : IPostData<T>, IPostData
{
// ... code omitted
public static implicit operator PostData<T>(string literalString);
}
I am unit testing a PersonIndexer class whose role is to call IndicesCreate with the correct index name "people" and the correct json body - I don't care about the last param. To do that I am mocking IElasticLowLevelClient using the Moq library.
Here is how PersonIndexer calls the low level client to create the index :
var result = _elasticClient.IndicesCreate<object>("people", "{ properties: {etc}}", null);
Here is what the PersonIndexerTest class verification would ideally look like (json parsing simplified):
_elasticClientMock.Verify(c => c.IndicesCreate<object>(
"people",
It.Is<string>(body =>
JObject.Parse(body).SelectToken("$.properties.name.type").ToString() == "string"
),
null
));
The issue is that Moq never sees this call because it is expecting PostData<object> to match the second argument type, not a string.
You would tell me to use the "real" underlying type in my clause : It.Is<PostData<object>>(...) but this raises another issue : the PostData class does not publicly expose the string I used as the implicit constructor, thus it prevents me from parsing the body of my index creation request.
Is there any trick I can use to verify the string passed ? Is there any way to setup the mock so that it reproduces the implicit operator's behavior ? Am I going in the wrong direction...?
PostDatahas an other implicit operator for byte arrays, and it has a different behavior. In fact, if instead of passing a string, I pass a byte array, the PostData instance will automatically make it available in its WrittenBytes property (it doesn't with the string operator). This is not very clean because it forces me to convert my nice string body expression to a bytearray in every indexer's implementation, but it's testable. I would prefer a mocking feature that bypasses the implicit operator.