javamockitobuilderfluent

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


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?


Solution

  • 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)