Fluent way (builder style) of creating a Mockito mock with method stubs

89 Views Asked by At

One of ways to create a Mockito mock in Java is to

  1. create the mock
  2. stubb the methods.

For instance like this:

// this code is just a imaginary proposal 
private Properties emptyProperties()  {
    Properties myMock = mock(Properties.class); // 1
    when(myMock.isEmpty()).thenReturn(true);    // 2
    when(myMock.size()).thenReturn(0);          // 2
    return myMock;
}

I would like to create this mock in a fluent way like this:

private Properties emptyProperties()  {
    return buildMock(Properties.class) // 1
      .when(myMock.isEmpty()).thenReturn(true)    // 2
      .when(myMock.size()).thenReturn(0)          // 2
}

Is there any mechanism in Mockito itself which allows to construct mocks like that? Or any other framework which could facilitate this way of construction?

EDIT: Motivation with bigger example.

Answering the comments, my motivation is to create a nested mocks in a fluent way. For instance instead of code:

        Questions.QuestionItem item = mock(Questions.QuestionItem.class);
        
        Questions questions = mock(Questions.class);
        when(questions.getItems()).thenReturn(Lists.newArrayList(item));

        QuestionController controller = mock(QuestionController.class);
        when(controller.getQuestions()).thenReturn(questions);

I would like to create something like:

        QuestionController controller = mock(QuestionController.class)
                .when(getQuestions()).thenReturn(
                        mock(Questions.class)
                                .when(getItems()).thenReturn(
                                        Lists.newArrayList(item))
                );

One of the motivation is to avoid creating local variables, especially where there are more nesting levels and more methods to mock.

EDIT 2

After using implementation of @knittl the code looks like what I am looking for:

QuestionController demoMock = FluentMock.mock(QuestionController.class)
        .when(QuestionController::getQuestions).thenReturn(
                FluentMock.mock(Questions.class)
                        .when(Questions::getItems)
                        .thenReturn(Lists.newArrayList(
                             mock(Questions.QuestionItem.class)))
                        .returnMock())
        .returnMock();

I just wonder if there is any library which supports this kind of FluentMock out of the box?

1

There are 1 best solutions below

2
knittl On BEST ANSWER

I don't think there's a benefit in avoiding a single line (you are not really saving lines anyway), but if you must do it, the following could be one way to do it. It is a thin wrapper around a Mockito mock object that allows applying a configuration to the mock object, then returns itself.

public class FluentMock<M> {

    private final M mock;

    private FluentMock(final M mock) {
        this.mock = mock;
    }

    public static <M> FluentMock<M> mock(final Class<M> cls) {
        return new FluentMock<>(Mockito.mock(cls));
    }

    public FluentMock<M> stub(final Consumer<? super M> stubber) {
        stubber.accept(mock);
        return this;
    }

    public M returnMock() {
        return mock;
    }
}

Usage:

@Test
void fluent_expression() {
    final DemoClass demoMock = FluentMock.mock(DemoClass.class)
            .stub(mock -> when(mock.getName()).thenReturn("skynet"))
            .stub(mock -> when(mock.getAge()).thenReturn(666))
            .returnMock();

    assertThat(demoMock.getName()).isEqualTo("skynet");
    assertThat(demoMock.getAge()).isEqualTo(666);
}

@Test
void fluent_statement() {
    final DemoClass demoMock = FluentMock.mock(DemoClass.class)
            .stub(mock -> {
                when(mock.getName()).thenReturn("skynet");
                when(mock.getAge()).thenReturn(666);
            })
            .returnMock();

    assertThat(demoMock.getName()).isEqualTo("skynet");
    assertThat(demoMock.getAge()).isEqualTo(666);
}

Not sure if that's what you were hoping for.


Alternatively, go full overboard and add all methods (that you need) from OngoingStubbing:

public class FluentMock<M> {

    private final M mock;

    private FluentMock(final M mock) {
        this.mock = mock;
    }

    public static <M> FluentMock<M> mock(final Class<M> cls) {
        return new FluentMock<>(Mockito.mock(cls));
    }

    public FluentMock<M> stub(final Consumer<? super M> stubber) {
        stubber.accept(mock);
        return this;
    }

    public <T> FluentStubber<T> when(final Function<? super M, T> call) {
        return new FluentStubber<>(call);
    }

    public M returnMock() {
        return mock;
    }

    public final class FluentStubber<T> {
        private final Function<? super M, T> call;

        private FluentStubber(final Function<? super M, T> call) {
            this.call = call;
        }

        public FluentMock<M> thenReturn(final T value) {
            Mockito.when(call.apply(mock)).thenReturn(value);
            return FluentMock.this;
        }
    }
}

Usage would then look like this:

@Test
void fluent_stubbing() {
    final DemoClass demoMock = FluentMock.mock(DemoClass.class)
            .when(DemoClass::getName).thenReturn("skynet")
            .when(DemoClass::getAge).thenReturn(666)
            .returnMock();

    assertThat(demoMock.getName()).isEqualTo("skynet");
    assertThat(demoMock.getAge()).isEqualTo(666);
}

Caveat: this doesn't allow you to add multiple stubbings to a single method, so you cannot do when(DemoClass::getName).thenReturn("skynet").thenReturn("T-800") with this kind of fluent builder (you'd have to go the stub(mock -> ...) route or just use plain Mockito)