1. Overview
In this tutorial, we’ll see how to mock nested method calls using Mockito stubs, specifically deep stubs. To learn more about testing with Mockito, check out our comprehensive Mockito series.
2. Explaining the Problem
In complex code, especially legacy code, it’s sometimes hard to initialize all the objects needed to unit test. It’s easy to introduce many dependencies that we don’t need in our tests. On the other hand, mocking those objects can lead to null pointer exceptions.
Let’s see a code example where we’ll explore the limitations of those two approaches.
For both, we’ll need some classes to test. First, let’s add a NewsArticle class:
public class NewsArticle {
String name;
String link;
public NewsArticle(String name, String link) {
this.name = name;
this.link = link;
}
// Usual getters and setters
}
Also, we’ll need a Reporter class:
public class Reporter {
String name;
NewsArticle latestArticle;
public Reporter(String name, NewsArticle latestArticle) {
this.name = name;
this.latestArticle = latestArticle;
}
// Usual getters and setters
}
And finally, let’s create a NewsAgency class:
public class NewsAgency {
List<Reporter> reporters;
public NewsAgency(List<Reporter> reporters) {
this.reporters = reporters;
}
public List<String> getLatestArticlesNames(){
List<String> results = new ArrayList<>();
for(Reporter reporter : this.reporters){
results.add(reporter.getLatestArticle().getName());
}
return results;
}
}
Understanding the relationship between them is important. First, a NewsArticle is reported by a Reporter. And a Reporter works for a NewsAgency.
NewsAgency contains a method getLatestArticlesNames() that returns the names of the latest articles written by all the reporters working with the NewsAgency. This method will be subject to our unit testing.
Let’s take a first stab at this unit test by initializing all the objects.
3. Initializing Objects
In our test, as a first approach, we’ll initialize all the objects:
public class NewsAgencyTest {
@Test
void getAllArticlesTest(){
String title1 = "new study reveals the dimension where the single socks disappear";
NewsArticle article1 = new NewsArticle(title1,"link1");
Reporter reporter1 = new Reporter("Tom", article1);
String title2 = "secret meeting of cats union against vacuum cleaners";
NewsArticle article2 = new NewsArticle(title2,"link2");
Reporter reporter2 = new Reporter("Maria", article2);
List<String> expectedResults = List.of(title1, title2);
NewsAgency newsAgency = new NewsAgency(List.of(reporter1, reporter2));
List<String> actualResults = newsAgency.getLatestArticlesNames();
assertEquals(expectedResults, actualResults);
}
}
We can see how the initialization of all objects can get tedious when our objects become more and more complex. As mocks serve exactly this purpose, we’ll use them to simplify and avoid cumbersome initializations.
4. Mocking Objects
Let’s use mocks to test the same method getLatestArticlesNames() :
@Test
void getAllArticlesTestWithMocks(){
Reporter mockReporter1 = mock(Reporter.class);
String title1 = "cow flying in London, royal guard still did not move";
when(mockReporter1.getLatestArticle().getName()).thenReturn(title1);
Reporter mockReporter2 = mock(Reporter.class);
String title2 = "drunk man accidentally runs for mayor and wins";
when(mockReporter2.getLatestArticle().getName()).thenReturn(title2);
NewsAgency newsAgency = new NewsAgency(List.of(mockReporter1, mockReporter2));
List<String> expectedResults = List.of(title1, title2);
assertEquals(newsAgency.getLatestArticlesNames(), expectedResults);
}
If we try to execute this test as is, we’ll receive a null pointer exception. The root cause is that the call to mockReporter1.getLastestArticle() returns null, which is an expected behavior: a mock is a nullified version of the object.
5. Using Deep Stubs
Deep stubs are an easy solution to mock nested calls. Deep stubs help us leverage the mocks and stubbing only the calls we need in our tests.
Let’s use it in our example, we’ll rewrite the unit test using mocks and deep stubs:
@Test
void getAllArticlesTestWithMocksAndDeepStubs(){
Reporter mockReporter1 = mock(Reporter.class, Mockito.RETURNS_DEEP_STUBS);
String title1 = "cow flying in London, royal guard still did not move";
when(mockReporter1.getLatestArticle().getName()).thenReturn(title1);
Reporter mockReporter2 = mock(Reporter.class, Mockito.RETURNS_DEEP_STUBS);
String title2 = "drunk man accidentally runs for mayor and wins";
when(mockReporter2.getLatestArticle().getName()).thenReturn(title2);
NewsAgency newsAgency = new NewsAgency(List.of(mockReporter1, mockReporter2));
List<String> expectedResults = List.of(title1, title2);
assertEquals(newsAgency.getLatestArticlesNames(), expectedResults);
}
Adding Mockito.RETURNS_DEEP_STUBS allowed us to access all nested methods and objects. In our code example, we did not need to mock multiple levels of objects in mockReporter1 to access mockReporter1.getLatestArticle().getName().
6. Conclusion
In this article, we learned how to use deep stubs to solve the issue of nested method calls with Mockito.
We should keep in mind that the necessity to use them is often a symptom of a violation of Demeter’s law, a guideline in object-oriented programming that favors low coupling and avoiding nested method calls. Therefore, deep stubs should be reserved for legacy code, and in clean modern code we should favor refactoring the nested calls.
The complete source code for the examples is available over on GitHub.