Mocking final methods and classes with Powermock
Tags:
1) Preface
Legacy code may seem overwhelming at times. In most cases, to be exact. A lot of times, there is even no one left from the original team that created it. No help, long gone or heavily outdated documentation, multi-thousand line monster classes fruitful in multi-hundred line methods that contain dozens upon dozens of logical paths. We have all been there and seen it. Due to these traits, the legacy code is tough to test. Sometimes impossible without special tools. PowerMock lends us a helping hand in this kind of situations where the methods under test are not crazy big but contain references to, hard to test language constructs like static methods, private methods and the subject of this post: the final methods and classes. Let us take a look at how we can handle them with the use of this powerful tool.
2) Powermock: mocking final method
We will start with an example where in our SUT, we are forced to call a final method. We do not want to invoke the implementation of that method as it is very complex and not really the point of interest of our test case. Let's look at the code example where we are querying the database of Auctions to find the most popular ones for a given categoryId and then using some additional logic to filter out the final result:
private static final Integer MOST_POPULAR_CAP = 10;
@Autowired private AuctionRepository auctionRepository;
@Autowired private PopularityResolver popularityResolver;
public List<Auction> getMostPopularAuctionsFromCategory(Integer categoryId) {
List<Auction> initialPopularAuctionCandidates = auctionRepository
.findByCategoryOrderByViewsDesc(categoryId, Integer.valueOf(20));
List<Auction> topAuctions = popularityResolver
.resolveWithCap(initialPopularAuctionCandidates, MOST_POPULAR_CAP);
return topAuctions;
}
In this SUT method, we are:
● (L8): Retrieving twenty most viewed active Auctions.
● (L11): Invoking the resolveWithCap method of PopularityResolver. This is a third-party written library, and we cannot make any changes to it, unfortunately. On top of that, it is a final method and queries resources that we do not want to invoke during our UT.
Despite these obstacles, we still need to pull off the test somehow. PowerMock enables us to stub the final method and omit the real implementation being invoked:
@RunWith(PowerMockRunner.class)
@PrepareForTest( { PopularityResolver.class })
public class BasicAuctionServiceTest {
@Mock private AuctionRepository auctionRepositoryStub;
@InjectMocks private BasicAuctionService basicAuctionServiceSUT;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldGetMostPopularAuctionsFromCategory() throws Exception{
// Arrange
Integer categoryId = Integer.valueOf(10);
List<Auction> initialAuctions = new ArrayList<>();
List<Auction> expectedAuctions = new ArrayList<>();
AuctionRepository auctionRepositoryStub = Mockito.mock(AuctionRepository.class);
Mockito.when(auctionRepositoryStub.findByCategoryOrderByViewsDesc(categoryId, Integer.valueOf(20)))
.thenReturn(initialAuctions);
PopularityResolver popularityResolverStub = PowerMockito.mock(PopularityResolver.class);
basicAuctionServiceSUT.setPopularityResolver(popularityResolverStub);
PowerMockito.when(popularityResolverStub.resolveWithCap(initialAuctions, Integer.valueOf(10)))
.thenReturn(expectedAuctions);
// Act
List<Auction> mostPopularAuctionsFromCategory = basicAuctionServiceSUT
.getMostPopularAuctionsFromCategory(categoryId);
// Assert
assertThat(mostPopularAuctionsFromCategory).isSameAs(expectedAuctions);
}
In this test method, we are:
● (L3): Preparing the PopularityResolver class for PowerMock to be able to stub its resolveWithCap method.
● (L25): Mocking the PopularityResolver class.
● (L27): Stubbing the final resolveWithCap method.
● (L35): Making sure that the returned list is as we expect it to be.
3) Powermock: mocking final method with wildcards
In this example, we will try to go through an alternative version of the previous code. Let us take a look:
@Autowired private AuctionRepository auctionRepository;
@Autowired private PopularityResolver popularityResolver;
@Autowired private Config config;
public List<Auction> getMostPopularAuctionsFromCategory(Integer categoryId) {
List<Auction> initialPopularAuctionCandidates = auctionRepository
.findByCategoryOrderByViewsDesc(categoryId, Integer.valueOf(20));
List<Auction> topAuctions = popularityResolver
.resolveWithCap(initialPopularAuctionCandidates,
config.getInt("MOST_POPULAR_CAP"));
return topAuctions;
}
As we can see how the MOST_POPULAR_CAP value is taken from an external properties file. We do not want to read a file, and in that case, we do not really care about expecting any particular value here. If it passes an Integer type check, it is sufficient:
@RunWith(PowerMockRunner.class)
@PrepareForTest( { PopularityResolver.class })
public class BasicAuctionServiceTest {
@Mock private AuctionRepository auctionRepositoryStub;
@Mock private Config configStub;
@InjectMocks private BasicAuctionService basicAuctionServiceSUT;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldGetMostPopularAuctionsFromCategory() throws Exception{
// Arrange
Integer categoryId = Integer.valueOf(10);
List<Auction> initialAuctions = new ArrayList<>();
List<Auction> expectedAuctions = new ArrayList<>();
AuctionRepository auctionRepositoryStub = Mockito.mock(AuctionRepository.class);
Mockito.when(auctionRepositoryStub.findByCategoryOrderByViewsDesc(categoryId, Integer.valueOf(20)))
.thenReturn(initialAuctions);
PopularityResolver popularityResolverStub = PowerMockito.mock(PopularityResolver.class);
basicAuctionServiceSUT.setPopularityResolver(popularityResolverStub);
PowerMockito.when(popularityResolverStub.resolveWithCap(
ArgumentMatchers.eq(initialAuctions), ArgumentMatchers.anyInt()))
.thenReturn(expectedAuctions);
// Act
List<Auction> mostPopularAuctionsFromCategory = basicAuctionServiceSUT
.getMostPopularAuctionsFromCategory(categoryId);
// Assert
assertThat(mostPopularAuctionsFromCategory).isSameAs(expectedAuctions);
}
In this test method, we are:
● (L7): Creating a stub from the Config class that normally would read properties file.
● (L29-31): Stubbing the resolveWithCap method so that it returns the expectedAcutions list when an object equal to initialAuctions is passed along with any Integer.
Take note that we do not need to explicitly stub here the config.getInt method. It would return by default a NULL value which is still fine for the ArgumentMatchers.anyInt method to pass.
4) Powermock: mocking final class
Sometimes it is not the method that causes the problem but the class itself. It is a common practice and 100% justified to mark utility classes as final. Usually the methods in such classes are marked as static, but in our fabulous legacy app someone insisted on creating the class object every time there is a need to invoke one of its public methods:
@Autowired private AuctionRepository auctionRepository;
@Autowired private PopularityResolver popularityResolver;
private CategoryUtilities categoryUtilities; // setter provided
public List<Auction> getMostPopularAuctionsFromCategory(Integer categoryId) {
List<Auction> initialPopularAuctionCandidates = auctionRepository
.findByCategoryOrderByViewsDesc(categoryId, Integer.valueOf(20));
Integer mostPopularCap = categoryUtilities.getCapForCategory(categoryId);
List<Auction> topAuctions = popularityResolver
.resolveWithCap(initialPopularAuctionCandidates, mostPopularCap);
return topAuctions;
}
Here we get the cap from CategoryUtilities.getCapForCategory method. Again we do not want to call this method as it uses some external resources which are out of the scope of this test case. The CategoryUtilities class is final though so we need to ask PowerMock for some extra assistance:
@RunWith(PowerMockRunner.class)
@PrepareForTest( { PopularityResolver.class, CategoryUtilities.class })
public class BasicAuctionServiceTest {
@Mock private AuctionRepository auctionRepositoryStub;
@InjectMocks private BasicAuctionService basicAuctionServiceSUT;
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
@Test
public void shouldGetMostPopularAuctionsFromCategory() throws Exception{
// Arrange
Integer categoryId = Integer.valueOf(10);
List<Auction> initialAuctions = new ArrayList<>();
List<Auction> expectedAuctions = new ArrayList<>();
AuctionRepository auctionRepositoryStub = Mockito.mock(AuctionRepository.class);
Mockito.when(auctionRepositoryStub.findByCategoryOrderByViewsDesc(categoryId, Integer.valueOf(20)))
.thenReturn(initialAuctions);
PopularityResolver popularityResolverStub = PowerMockito.mock(PopularityResolver.class);
CategoryUtilities categoryUtilitiesStub = PowerMockito.mock(CategoryUtilities.class);
basicAuctionServiceSUT.setPopularityResolver(popularityResolverStub);
basicAuctionServiceSUT.setCategoryUtilities(categoryUtilitiesStub);
PowerMockito.when(popularityResolverStub.resolveWithCap(
ArgumentMatchers.eq(initialAuctions), ArgumentMatchers.anyInt()))
.thenReturn(expectedAuctions);
// Act
List<Auction> mostPopularAuctionsFromCategory = basicAuctionServiceSUT
.getMostPopularAuctionsFromCategory(categoryId);
// Assert
assertThat(mostPopularAuctionsFromCategory).isSameAs(expectedAuctions);
}
Here we have to add our CategoryUtilities class to the @PrepareForTest annotation along with the existing PopularityResolver. Also, on (L26), we mock the final class. Again we do not care what the exact Integer value is passed into the resolveWithCap method, so the Mockito default return value is sufficient.
5) Conclusion
We have seen PowerMock in action when it comes to dealing with final methods and classes. We must remember that in the non-legacy code, there is normally a better way to design your solution without the need to use finals directly. Anyway if there is no other option, PowerMock helps us in achieving that final lift!