One of ways to create a Mockito mock in Java is to
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?
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)