Maciej
Walkowiak

Freelance Architect & Developer
Java, Spring, AWS

Mocking fluent interfaces with Mockito

October 26, 2017

In last years I see more and more libraries providing APIs in the form of fluent interfaces. If you are not sure what a fluent interface is, I recommend reading Martin Fowler’s article from 2005, where he coined this term.

Wikipedia describes fluent interfaces in following way:
In software engineering, a fluent interface is a method for constructing object oriented APIs, where the readability of the source code is close to that of ordinary written prose.

The example of such an interface we can find in MongoOperations interface from Spring Data MongoDB:

mongoOperations.query(SWCharacter.class)
               .inCollection("star-wars")
               .as(Jedi.class)
               .matching(query(where("name").is("luke")))
               .one();

Recently I came across a short discussion on Twitter on how painful is mocking fluent interfaces:

Let’s have a look how we can approach this problem with the most popular mocking library for Java - Mockito. Typically, mocking the above code sample would result in the following:

FluentMongoOperations mongoOperations = mock(FluentMongoOperations.class);
ExecutableFind<SWCharacter> find = mock(ExecutableFind.class);
FindWithProjection<SWCharacter> findWithProjection = mock(FindWithProjection.class);
FindWithQuery<Jedi> findWithQuery = mock(FindWithQuery.class);
TerminatingFind<Jedi> terminatingFind = mock(TerminatingFind.class);

when(mongoOperations.query(SWCharacter.class)).thenReturn(find);
when(find.inCollection("star-wars")).thenReturn(findWithProjection);
when(findWithProjection.as(Jedi.class)).thenReturn(findWithQuery);
when(findWithQuery.matching(query(where("name").is("luke")))).thenReturn(terminatingFind);
when(terminatingFind.one()).thenReturn(Optional.of(jedi));

While many things in software engineering are a matter of taste, I am sure that it would be difficult to find anyone who would say that this code qualifies as “clean”.

Fortunately, there is a better approach. Mockito allows setting an answer mode when mocks are created.

Answer specifies an action that is executed and a return value that is returned when you interact with the mock.
  • CALLS_REAL_METHODS - An answer that calls the real methods (used for partial mocks).
  • RETURNS_DEEP_STUBS - An answer that returns deep stubs (not mocks).
  • RETURNS_DEFAULTS - The default configured answer of every mock.
  • RETURNS_MOCKS - An answer that returns mocks (not stubs).
  • RETURNS_SELF - An answer that tries to return itself.
  • RETURNS_SMART_NULLS - An answer that returns smart-nulls.

Mocking fluent interfaces works the best with Answers#RETURN_DEEP_STUBS. Once we declare mock to return deep stubs:

  • imperatively:
FluentMongoOperations mongoOperations = mock(FluentMongoOperations.class, 
                                             Mockito.RETURNS_DEEP_STUBS);
  • or with annotations:
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
FluentMongoOperations mongoOperations;

Code from the beginning of the post gets simplified to:

when(mongoOperations.query(SWCharacter.class)
                    .inCollection("star-wars")
                    .as(Jedi.class)
                    .matching(query(where("name").is("luke")))
                    .one()).thenReturn(Optional.of(jedi));