Mock? What, When, How?

No Comments

Mocking frameworks, such as Mockito, are powerful tools for testing and test-driven development (TDD) in particular. But sometimes, it can be a little confusing to decide what to mock and how to mock.

When I browse through other people’s (or even my own) test code, I sometimes see a construct like this:

import com.thirdparty.lib.PricingService;
...
 
PricingService service = mock(PricingService.class);
PriceDto dto = mock(PriceDto.class);
when(dto.getValue()).thenReturn(99.9);
when(service.getPrice(any(), LocalDate.now())).thenReturn(dto);
 
CashRegister sut = new CashRegister(service);
sut.enter(new Product());
 
assertThat(sut.receiptItemCount(), is(1));

There are several issues with this code.
The CashRegister in the example above has a dependency called PricingService. A dependency is normally a good candidate for a mock. In this case however, PricingService is an external dependency.

Only mock types that you own

External types have dependencies on their own. I also might not fully understand how they work and they might even change their behavior in a next version. Mocking third-party code can be a problem, and that’s why I avoid it.

Instead of mocking the PricingService directly, I first write an Adapter for it:

interface InventorySystem { 
  // instead of “PricingServiceAdapter”, I choose a domain name
  PriceDto getCurrentPrice(Product product);
}

And then use it inside the test:

InventorySystem inventory = mock(InventorySystem.class);

In production, I can delegate to the third-party class:

class OnlineInventorySystem implements InventorySystem {
 
  final PricingService service;
 
  OnlineInventorySystem(PricingService service) {
    this.service = service;
  }
 
  PriceDto getCurrentPrice(Product product) {
    return service.getPrice(product, LocalDate.now());
  }
}

Introducing an Adapter makes the tests and the production code more robust to changes of the third-party classes.
I can use my own domain terms for methods and the class itself instead of using what the third party dictates. Even better, I might notice that I’m always calling a method with the same arguments, so I can account for that in the adapter.
Another benefit of this pattern is that the relationship between my own code and the third-party code becomes clearer and is only visible at a single place in the code.
If the external type is easy to mock or what I need is just a dummy, mocking those boundary classes is still an okay thing to do. A good indicator of the need for an adapter is an overly complicated test setup.

Don’t mock values

A PriceDto is mocked to return it from the service. This type is a value. It has no identity and its fields are most probably immutable. Values should not be mocked.
But why does it matter? Mocking is a technique that is used to make the relationships and interactions between objects visible.
It’s not a tool to make it easier to instantiate complex objects. Getting the value of a price is not an interaction. If it’s too complicated to create a PriceDto (e.g. because it has too many constructor parameters or doesn’t follow the Single Responsibility Principle), then I should improve the design or consider using a Builder.

So, instead of mocking out the price, I just use the constructor directly:

InventorySystem service = mock(InventorySystem.class);
PriceDto dto = new PriceDto(99.9);
when(service.getCurrentPrice(any())).thenReturn(dto);
 
CashRegister sut = new CashRegister(service);
sut.enter(new Product());
 
assertThat(sut.receiptItemCount(), is(1));

A value is not only immutable, but it’s also a concrete class. There is usually no interface specified. Even if it’s not a value, mocking concrete classes can be a problem for the same reasons that mocking values is a problem.

Avoid mocking concrete classes

When I mock a concrete class, I’m not saying (or rather discovering, since I’m driving my design by tests) anything about the relationship of the different types. I might even hide the fact that the types don’t really belong so close together.
If an object has five public methods, but my current object under test is using only two of them (the Interface Segregation Principle is violated), it’s a good indicator that I need to create another type. This extra relationship is harder to spot when I use concrete objects instead of interfaces.
Additionally, if I mock a method of an object, but forget a different method that the object under test also calls, the test might fail with obscure errors.

public class Discount {
  public double factor() {
   ... 
  }
 
  public int discount(int price) {
    return (int) (price * factor());
  }
}
...
Discount discount = mock(Discount.class);
when(discount.factor()).thenReturn(0.5d);
 
int discountedPrice = discount.discount(100);
assertThat(discountedPrice, is(50));

This assertion will certainly succeed, right? Well, it won’t. Creating the mock like this will return defaults for all methods that have not been mocked. This means that the discount method will just return 0 (so-called partial mocks are a workaround).

In the discount example, the object under test itself has been mocked, which is a bad idea to begin with. It’s the interactions I want, after all. It’s therefore quite easy to notice the issue and fix the test. But if Discount was a dependency I would have a much harder time to discover the problem.
In most cases, I’ll be better off without mocking concrete classes.

All the issues I have described lead me to my final point. My current language of choice is Kotlin. It has a lot of great features of which I don’t want to give a detailed explanation here. Others have done it already.

Kotlin can help

One aspect, however, is interesting.
In Kotlin, classes and methods are final by default. If I want to inherit from a concrete class, I must make this explicit by declaring it as open. It’s therefore not possible to accidentaly mock concrete classes.
This also means that value objects can’t be mocked. Because of Kotlin’s data classes, it’s often easier than in Java to create those values anyway. I won’t be tempted to mock them so easily.
While some people are unhappy because of Kotlin’s “unmockable” nature, I think that this feature of Kotlin enables a better design, which is especially useful and visible while using TDD.

I can break the rules in situations where it’s necessary. This is most likely the case when I work with legacy code or code that I don’t control.

To learn more about all these topics, I highly recommend the classic “Growing Object-Oriented Software, Guided by Tests” by Freeman and Pryce. The Mockito documentation also has a section on how to write good tests with mocks.

Lovis Möller

Lovis Möller, made in Hamburg, always tries to uncover new ways of developing software and solving problems.

Post by Lovis Möller

Kotlin

Einführung in Kotlin Coroutines

More content about Agile Testing

Comment

Your email address will not be published. Required fields are marked *