elasticsearch-net icon indicating copy to clipboard operation
elasticsearch-net copied to clipboard

Unit test "story" documentation

Open codebrain opened this issue 4 years ago • 5 comments

A number of users are asking how to mock/stub/unit/integration test using the client.

It would be good to have a story (unit-testing.md?) where we can point users, and potentially look to release this as a blog post in order to reach a wider audience.

codebrain avatar Dec 19 '19 07:12 codebrain

We also need to mock IndicesNamespace and we miss mock for TotalHits: searchResponse.HitsMetadata.Total.Value

remote-specialist avatar Apr 02 '20 07:04 remote-specialist

Do we have any update on this? Anyone got a chance to write sample unit test cases to showcase the handling of ElasticClient Namespaces?

UdaySamineni avatar Oct 28 '20 13:10 UdaySamineni

As a user of this library I find the difficulty in mocking the library's presented types, the solutions presented as "work arounds," and the reasoning behind the choices made in this regard lack luster at best. You mock the integration points with libraries such as this to confirm they're being called as designed because very often (always?) both the call and response received are tied directly to application behavior at higher levels in the application stack. A developer who changes the nature of such a call should have a unit test which fails to notify them that they have potentially altered the behavior of the application. This is the fundamental purpose of a unit test. As designed, you've more or less made this level of granularity impossible or at best extremely difficult. Yes you can create a wrapper class around the NEST client (which is itself a wrapper) and mock the wrapper, but you're still trusting that the wrapper itself is functioning as designed which as the wrapper ages in the SDLC it will increase in likelihood that this will not be the case. This leaves the only option for testing the wrapper to be integration style tests which communicate over the wire with a mock or real elasticsearch server or using the serialization method outlined in other issues to serialize a mock response. All of these things seem undesirable as a customer. To be honest if it wasn't because of the unique capabilities of elasticsearch itself, for this reason alone I would not be using this product. I accept that there might be something completely obvious that I'm missing, but from a customer perspective that lack of discoverability would also be an issue if that's the case.

michaelmccord avatar Jan 24 '21 19:01 michaelmccord

+1 to this - it's very unclear how users are intended to mock interactions with this client for unit testing our code. I agree with your & russcam's comments on #4287 that it leaves you with an explosion of single-implementation interface classes, but that's a fundamental weakness of C#

hauntingEcho avatar Jan 29 '21 21:01 hauntingEcho

For anybody else looking for a solution, you can use reflection shenanigans to at least mock out the underlying transport although you end up tied into the implementation of the library to an uncomfortable degree. Hopefully somebody can provide a better solution than this ugly hack for Moq:

private static void MockAliasResponse(Mock<IElasticLowLevelClient> client, string alias, StringResponse response)
{
    var mockTransport = new Mock<ITransport<IConnectionConfigurationValues>>(MockBehavior.Strict);
    mockTransport.Setup(t => t.Settings).Returns(new ConnectionConfiguration());

    mockTransport
        .Setup(t => t.RequestAsync<StringResponse>(HttpMethod.GET, $"_alias/{alias}", It.IsAny<CancellationToken>(), null, null))
        .Returns(Task.FromResult(response));

    ElasticLowLevelClient secondaryClient = new ElasticLowLevelClient(mockTransport.Object);
    // workaround for https://github.com/elastic/elasticsearch-net/issues/4293
    LowLevelIndicesNamespace indicesNamespace = (LowLevelIndicesNamespace)typeof(LowLevelIndicesNamespace)
        .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(ElasticLowLevelClient) }, null)
        .Invoke(new object[] { secondaryClient });

    client.SetupGet(c => c.Indices).Returns(indicesNamespace);
}

hauntingEcho avatar Jan 29 '21 22:01 hauntingEcho