How to mock an Elasticsearch Java Client?
Asked Answered
S

2

12

Do you know how to propertly mock the Elasticsearch Java Client? Currently to mock the following request in Java:

SearchResponse response = client.prepareSearch(index)
                .setTypes(type)
                .setFrom(0).setSize(MAX_SIZE)
                .execute()
                .actionGet();
SearchHit[] hits = response.getHits().getHits();

I have to mock:

  • client.prepareSearch
  • SearchRequestBuilder:
    • builder.execute
    • builder.setSize
    • builder.setFrom
    • builder.setTypes
  • SearchResponse:
    • action.actionGet
  • SearchResponse:
    • response.getHits
    • searchHits.getHits

So my test looks like:

SearchHit[] hits = ..........;

SearchHits searchHits = mock(SearchHits.class);
when(searchHits.getHits()).thenReturn(hits);

SearchResponse response = mock(SearchResponse.class);
when(response.getHits()).thenReturn(searchHits);

ListenableActionFuture<SearchResponse> action = mock(ListenableActionFuture.class);
when(action.actionGet()).thenReturn(response);

SearchRequestBuilder builder = mock(SearchRequestBuilder.class);
when(builder.setTypes(anyString())).thenReturn(builder);
when(builder.setFrom(anyInt())).thenReturn(builder);
when(builder.setSize(anyInt())).thenReturn(builder);
when(builder.execute()).thenReturn(action);

when(client.prepareSearch(index)).thenReturn(builder);

Ugly... So I would like to known if there is a more "elegant way" to mock this code.

Thanks

Suction answered 31/3, 2015 at 12:56 Comment(1)
For what it's worth, I think what you are going too low-level. You should only be testing your code, not checking to see whether Elasticsearch works: assume it does. So I imagine you have a method wrapping all this code: e.g. public String[] search(searchParams..) { SearchResponse response = client.prepareSearch(index).. etc; SearchHit[] hits = response.getHits().getHits(); return hits; }. Your test should mock out your search method and return mock String[] results for different inputs.Aspergillus
H
3

I've come across a similar problem when mocking builders, so I thought I'd experiment to see if there is a nicer way.

As Mr Spoon said, it's probably nicer if you can avoid doing do this in the first place as it is not your code and could be assumed to "just work", but I thought I'd give it a go anyway.

I've come up with a (maybe crude) way of doing it by using "default answers" in Mockito. I'm still deciding if I like it or not.

Here's my builder...

public class MyBuilder {

    private StringBuilder my;

    public MyBuilder() {
        my = new StringBuilder();
    }

    public MyBuilder name(String name) {
        my.append("[name=").append(name).append("]");
        return this;
    }

    public MyBuilder age(String age) {
        my.append("[age=").append(age).append("]");
        return this;
    }

    public String create() {
        return my.toString();
    }
}

(Pretty basic right?)

I got my test to look something like this...

// Create a "BuilderMocker" (any better name suggestions welcome!) 
BuilderMocker<MyBuilder> mocker = BuilderMocker.forClass(MyBuilder.class);
// Get the actual mock builder
MyBuilder builder = mocker.build();

// expect this chain of method calls...
mocker.expect().name("[NAME]").age("[AGE]");

// expect this end-of-chain method call...
Mockito.when(builder.create()).thenReturn("[ARGH!]");

Now if I do the following...

System.out.println(builder.name("[NAME]").age("[AGE]").create());

...I expect "[ARGH!]" to be output.

If I altered the last line...

System.out.println(builder.name("[NOT THIS NAME]").age("[AGE]").create());

...then I expect it to break with a NullPointerException.

Here is the actual "BuilderMocker"...

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;

import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class BuilderMocker<T> {

    private Class<T> clazz;
    private T recorder;
    private T mock;

    // Create a BuilderMocker for the class
    public static <T> BuilderMocker<T> forClass(Class<T> clazz) {
        return new BuilderMocker<T>(clazz);
    }

    private BuilderMocker(Class<T> clazz) {
        this.clazz = clazz;
        this.mock = mock(clazz);
        createRecorder();
    }

    // Sets up the "recorder"
    private void createRecorder() {
        recorder = mock(clazz, withSettings().defaultAnswer(new Answer<Object>() {

            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                // If it is a chained method...
                if (invocation.getMethod().getReturnType().equals(clazz)) {
                    // Set expectation on the "real" mock...
                    when(invocation.getMethod().invoke(mock, invocation.getArguments())).thenReturn(mock);
                    return recorder;
                }
                return null;
            }

        }));
    }

    // Use this to "record" the expected method chain
    public T expect() {
        return recorder;
    }

    // Use this to get the "real" mock...
    public T build() {
        return mock;
    }
}

Not sure if there is a "built-in" way of doing this in Mockito, but this seems to work.

Himmler answered 31/3, 2015 at 14:14 Comment(1)
I like this approach. I should develop my builders as library and reuse these classes along my tests. Thx!Suction
B
0

I too have a similar problem in Opensearch How to write java unit test for OpenSearchClient APIs and am looking for a way to mock the OpensearchCLient.

I though am able to mock the response successfully in a different test usecase. I used mockito to mock the client.<API_NAME> and return response object that was created as below; MsearchResponse.Builder mSearchResponseBuilder = null;

List<MultiSearchResponseItem<Map>> multiSearchResponseItemList = null;
        MultiSearchResponseItem.Builder<Map> multiSearchResponseItemBuilder = null;
        MultiSearchItem.Builder<Map> multiSearchItemBuilder = null;
        HitsMetadata.Builder<Map> hitsMetadataBuilder = null;
        List<Hit<Map>> hitItemList = null;
        Hit.Builder<Map> hitBuilder = null;
        Map<String, String> sourceMap = null;
        ShardStatistics.Builder shardStatisticsBuilder = null;
        MsearchResponse<Map> mSearchResponse = null;

        sourceMap.put("testkey", "test");

        hitBuilder = hitBuilder.seqNo(Long.valueOf(1)).source(sourceMap).index("testIndexName").id("testId");

        hitItemList.add(hitBuilder.build());

        hitsMetadataBuilder = hitsMetadataBuilder.hits(hitItemList);

        multiSearchItemBuilder = multiSearchItemBuilder.hits(hitsMetadataBuilder.build()).took(Long.valueOf(2));

        shardStatisticsBuilder = shardStatisticsBuilder.failed(0).successful(1).total(1);
        multiSearchItemBuilder = multiSearchItemBuilder.timedOut(false).shards(shardStatisticsBuilder.build()).status(201);

        multiSearchResponseItemBuilder = (Builder<Map>) multiSearchResponseItemBuilder.result(multiSearchItemBuilder.build());

        multiSearchResponseItemList.add(multiSearchResponseItemBuilder.build());

        mSearchResponseBuilder = mSearchResponseBuilder.responses(multiSearchResponseItemList).took(2);

        MsearchResponse<Map> mSearchResponse = mSearchResponseBuilder.build();
Mockito.when(mockObject.getServiceAPIReturningMsearchResponse(ArgumentMatchers.<RequestItem>anyList()))
                .thenReturn(mSearchResponse);

I hope this gets you a step closer to the solution. Cheers.

Brinson answered 16/11, 2023 at 5:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.