1. Introduction
Fluent APIs are a software engineering design technique based on method chaining for building concise, readable and eloquent interfaces.
They’re often used for builders, factories and other creational design patterns. In recent times they’ve become increasingly popular with the evolution of Java and can be found in popular APIs such as the Java Stream API and Mockito testing framework.
Nevertheless, mocking Fluent APIs can be painful as we often need to set up a complex hierarchy of mock objects.
In this tutorial, we’ll take a look at how we can avoid this using a great feature of Mockito.
2. A Simple Fluent API
In this tutorial, we’ll use the builder design pattern to illustrate a simple fluent API for constructing a pizza object:
Pizza pizza = new Pizza
.PizzaBuilder("Margherita")
.size(PizzaSize.LARGE)
.withExtaTopping("Mushroom")
.withStuffedCrust(false)
.willCollect(true)
.applyDiscount(20)
.build();
As we can see, we’ve created an easy to understand API which reads like a DSL and allows us to create a Pizza object with various characteristics.
Now we’ll define a simple service class that uses our builder. This will be the class we’re going to test later on:
public class PizzaService {
private Pizza.PizzaBuilder builder;
public PizzaService(Pizza.PizzaBuilder builder) {
this.builder = builder;
}
public Pizza orderHouseSpecial() {
return builder.name("Special")
.size(PizzaSize.LARGE)
.withExtraTopping("Mushrooms")
.withStuffedCrust(true)
.withExtraTopping("Chilli")
.willCollect(true)
.applyDiscount(20)
.build();
}
}
Our service is pretty simple and contains one method called orderHouseSpecial. As the name implies, we can use this method to build a special pizza with some predefined properties.
3. Traditional Mocking
Stubbing with mocks in the traditional way is going to require that we create eight mock PizzaBuilder objects. We’ll need a mock for the PizzaBuilder returned by the name method, then a mock for the PizzaBuilder returned by the size method, etc. We’ll continue in this fashion until we satisfy all the method calls in our fluent API chain.
Let’s now take a look at how we might write a unit test to test our service method using conventional Mockito mocks:
@Test
public void givenTraditonalMocking_whenServiceInvoked_thenPizzaIsBuilt() {
PizzaBuilder nameBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
PizzaBuilder sizeBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
PizzaBuilder firstToppingBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
PizzaBuilder secondToppingBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
PizzaBuilder stuffedBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
PizzaBuilder willCollectBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
PizzaBuilder discountBuilder = Mockito.mock(Pizza.PizzaBuilder.class);
PizzaBuilder builder = Mockito.mock(Pizza.PizzaBuilder.class);
when(builder.name(anyString())).thenReturn(nameBuilder);
when(nameBuilder.size(any(Pizza.PizzaSize.class))).thenReturn(sizeBuilder);
when(sizeBuilder.withExtraTopping(anyString())).thenReturn(firstToppingBuilder);
when(firstToppingBuilder.withStuffedCrust(anyBoolean())).thenReturn(stuffedBuilder);
when(stuffedBuilder.withExtraTopping(anyString())).thenReturn(secondToppingBuilder);
when(secondToppingBuilder.willCollect(anyBoolean())).thenReturn(willCollectBuilder);
when(willCollectBuilder.applyDiscount(anyInt())).thenReturn(discountBuilder);
when(discountBuilder.build()).thenReturn(expectedPizza);
PizzaService service = new PizzaService(builder);
Pizza pizza = service.orderHouseSpecial();
assertEquals("Expected Pizza", expectedPizza, pizza);
verify(builder).name(stringCaptor.capture());
assertEquals("Pizza name: ", "Special", stringCaptor.getValue());
// rest of test verification
}
In this example, we need to mock the PizzaBuilder that we supply to the PizzaService. As we can see, this is no trivial task as we need to return a mock, which will return a mock for each call in our fluent API.
This leads to a complicated hierarchy of mock objects which is difficult to understand and can be tricky to maintain.
4. Deep Stubbing to the Rescue
Thankfully, Mockito provides a really neat feature called deep stubbing which allows us to specify an Answer mode when we create a mock.
To make a deep stub, we simply add the Mockito.RETURNS_DEEP_STUBS constant as an additional argument when we create a mock:
@Test
public void givenDeepMocks_whenServiceInvoked_thenPizzaIsBuilt() {
PizzaBuilder builder = Mockito.mock(Pizza.PizzaBuilder.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(builder.name(anyString())
.size(any(Pizza.PizzaSize.class))
.withExtraTopping(anyString())
.withStuffedCrust(anyBoolean())
.withExtraTopping(anyString())
.willCollect(anyBoolean())
.applyDiscount(anyInt())
.build())
.thenReturn(expectedPizza);
PizzaService service = new PizzaService(builder);
Pizza pizza = service.orderHouseSpecial();
assertEquals("Expected Pizza", expectedPizza, pizza);
}
By using the Mockito.RETURNS_DEEP_STUBS argument, we tell Mockito to make a kind of deep mock. This makes it possible to mock the result of a complete method chain or in our case fluent API in one go.
This leads to a much more elegant solution and a test that is much easier to understand than the one we saw in the previous section. In essence, we avoid the need to create a complex hierarchy of mock objects.
We can also use this answer mode directly with the @Mock annotation:
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private PizzaBuilder anotherBuilder;
One point to note is that verification will only work with the last mock in the chain.
5. Conclusion
In this quick tutorial, we’ve seen how we can use Mockito to mock a simple fluent API. First, we looked at a traditional mocking approach and understood the difficulties associated with this method.
Then we looked at an example using a little known feature of Mockito called deep stubs which permits a more elegant way to mock our fluent APIs.
As always, the full source code of the article is available over on GitHub.