Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

Mock? What, When, How?

16.3.2018 | 5 minutes of reading time

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:

1import com.thirdparty.lib.PricingService;
2...
3 
4PricingService service = mock(PricingService.class);
5PriceDto dto = mock(PriceDto.class);
6when(dto.getValue()).thenReturn(99.9);
7when(service.getPrice(any(), LocalDate.now())).thenReturn(dto);
8 
9CashRegister sut = new CashRegister(service);
10sut.enter(new Product());
11 
12assertThat(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:

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

And then use it inside the test:

1InventorySystem inventory = mock(InventorySystem.class);

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

1class OnlineInventorySystem implements InventorySystem {
2 
3  final PricingService service;
4 
5  OnlineInventorySystem(PricingService service) {
6    this.service = service;
7  }
8 
9  PriceDto getCurrentPrice(Product product) {
10    return service.getPrice(product, LocalDate.now());
11  }
12}

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:

1InventorySystem service = mock(InventorySystem.class);
2PriceDto dto = new PriceDto(99.9);
3when(service.getCurrentPrice(any())).thenReturn(dto);
4 
5CashRegister sut = new CashRegister(service);
6sut.enter(new Product());
7 
8assertThat(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.

1public class Discount {
2  public double factor() {
3   ... 
4  }
5 
6  public int discount(int price) {
7    return (int) (price * factor());
8  }
9}
10...
11Discount discount = mock(Discount.class);
12when(discount.factor()).thenReturn(0.5d);
13 
14int discountedPrice = discount.discount(100);
15assertThat(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 .

share post

Likes

0

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.