Mockito is cool, but in some cases workarounds are required to resolve certain situations. These workarounds force you to alter your code is order to be testable, but for a person who reads the code may find some of it not that useful, until he is explained why it was done this way. I will describe some of the cases I’m referring to.
Constructor calls
How many times do we construct objects using new constructor? Almost every time. In mockito usually a workaround is required in order to test this. The most common case is to build the object through some kind of builder or factory. That way we can just mock the method that builds it, but that builder/factory does not make much sense in the code. In order to be able to test QuoteProvider, instead of just using the constructor call we need to wrap it into a ‘factory’ and call getInstance() on it. When testing we will mock the QuoteProvider and use setInstance() to set the mocked instance.
public class QuoteProvider {
private static QuoteProvider INSTANCE = new QuoteProvider();
public static QuoteProvider getInstance() {
return INSTANCE;
}
public void setInstance(QuoteProvider quoteProvider){
INSTANCE = quoteProvider;
}
}
As you can see setInstance() method has no real use except for tests.
Static methods and objects
Everyone has these. They are especially used in utilities classes. As we know static means class not instance.
Final methods
Final methods are methods that cannot be overriden. So it’s normal that Mockito cannot create mocks from it. The same goes for final classes.
Private methods
Encapsulation. That’s why we have private keyword. What you can do here is to test the private method in “integration” with the public one that uses it. But not much else. These can be verified when a spy it’s being used.
PowerMock
How can PowerMock help us? It help us in the way it resolves these situations at the byte code level by manipulating it using a custom class loader. So no code changes required. It’s similar in this way with mutation testing. Expectations can be still verified.
Let’s consider this
public class QuotesCentral {
private MessageServer messageServer = null;
private final QuoteProvider quoteProvider = QuoteProvider.getInstance();
public List getQuotes() {
return quoteProvider.generateQuotes();
}
public List findByAuthor(final String author) {
return getQuotes().stream().filter(q -> q.getAuthor().equals(author))
.collect(Collectors.toList());
}
private MessageServer getMessageServer() {
return Optional.ofNullable(messageServer)
.orElseGet(() -> messageServer = new MessageServer());
}
private boolean publishQuotes(List quotes) {
try {
getMessageServer().connect();
return getMessageServer().publish(quotes);
} finally {
getMessageServer().disconnect();
}
}
public boolean publishQuotesByAuthor(String name) {
return publishQuotes(findByAuthor(name));
}
}
We have the setup of the test
@Before
public void setup() throws Exception {
PowerMockito.mockStatic(QuoteProvider.class);
Mockito.when(QuoteProvider.getInstance()).thenReturn(quoteProvider);
Mockito.when(quoteProvider.generateQuotes()).thenReturn(QuoteGenerator.QUOTES);
quotesCentral = new QuotesCentral();
PowerMockito.verifyStatic();
QuoteProvider.getInstance();
}
and one of the test methods
@Test
public void testPublishQuotesByAuthor() throws Exception {
ArgumentCaptor quotesCaptor = ArgumentCaptor.forClass(List.class);
String author = "Mark Twain";
//stub method calls
PowerMockito.whenNew(MessageServer.class).withNoArguments().thenReturn(messageServer);
Mockito.doNothing().when(messageServer).connect();
Mockito.doNothing().when(messageServer).disconnect();
Mockito.when(messageServer.publish(any(List.class))).thenReturn(true);
quotesCentral.publishQuotesByAuthor(author);
//verify interactions
PowerMockito.verifyNew(MessageServer.class).withNoArguments();
Mockito.verify(messageServer).connect();
Mockito.verify(messageServer).publish(quotesCaptor.capture());
assertThat(quotesCaptor.getValue()).as("There should be 2 quotes").hasSize(2);
assertThat(quotesCaptor.getValue()).as("The author should be " + author)
.extracting("author").allMatch(s -> s.equals(author));
Mockito.verify(messageServer).disconnect();
}
It can be seen where PowerMockito is being used. It’s worth mentioning that each static call should be preceded by a
PowerMockito.verifyStatic();
PowerMock is a powerful tool, but its intended audience are experienced developers who understand mocking. In wrong hands it can do more harm than good. So when deciding to use it or not take a look at the team experience first. This is mainly intended for situations when there’s no dependency injection available. And finally it’s a matter of flavor.
Like this:
Like Loading...