logo
Solutionlegacy

Mocking object creation with PowerMock

Views: 339 Created: 2019-02-16 Read time: 3 minutes
Post Preview
Tags:

1)     Preface

We have been given a task to handle some of our legacy code new changes. We were also told by our team lead that we should cover with unit tests whatever we amend due to the latest disposition by the solutions architect (which is the right decision). It so happens that our technical lead does not really like refactoring and significant structural changes made to that application. I am screwed you think to yourself. Mandatory tests to a legacy code change with no significant structural changes. That is just great, goodbye to the sprouting method. How am I going to mock all those objects that are created on the fly as no one had dependency injection in mind back when this application was under development.

 

Thankfully there is a tool that can lend us a hand in this situation. PowerMock offers a slick and easy to implement way of intercepting object creation and replacing it with a pre-configured stub. Let's dive right into it.

 

Note
Note

 

2)     Powermock: mocking no-arg constructor

Let us go through a basic scenario where we need to handle an object created with a constructor that does not accept any arguments. Here, in the Auction House application, we will try to offer a Insurance option. The main logic resides in the InsuranceCalculator class, which comes from a third-party library, and we cannot simply use it in our DI engine. Long story short, let's take a look at the SUT:

 

SUT Code


public Insurance offerInsurance(Integer auctionId, Integer buyerId) {
	Buyer buyer = buyerRepository.findById(buyerId).get();
	Auction auction = auctionRepository.findById(auctionId).get();

	InsuranceCalculator insuranceCalculator = new InsuranceCalculator();

	if(!buyer.hasInsuranceForAuction(auctionId)){
		return insuranceCalculator.generateQuote(
				auction.getProductWorth(), buyer.getCreditScore());
	}

	return null;
}

In this SUT method, we are:

       (L6):  Creating a new instance of the InsuranceCalculator.

       (L8):  Making sure that the Buyer does not have insurance yet for this particular auctionId.

       (L9):  Generating a quote for the buyer based on the auction.getProductWorth() and buyer.getCreditScore().

With plain Mockito we are not really able to test the happy path here unless we do some refactoring. Let us assume that we are not allowed to do that. PowerMock lends us a hand in this situation. Let's see how:

 

Test Code


@RunWith(PowerMockRunner.class)
@PrepareForTest( { BasicAuctionService.class })
public class BasicAuctionServiceTest {

    @Mock private BuyerRepository buyerRepositoryStub;
    @Mock private AuctionRepository auctionRepositoryStub;
    @Mock private InsuranceCalculator insuranceCalculatorStub;

    @InjectMocks
    private BasicAuctionService basicAuctionService;

@Test
public void shouldCalculateInsuranceForBuyer() throws Exception{
	// Arrange
	PowerMockito.whenNew(InsuranceCalculator.class)
			.withNoArguments()
			.thenReturn(insuranceCalculatorStub);

	Integer auctionId = Integer.valueOf(10);
	Integer buyerId = Integer.valueOf(4);
	Integer buyerCreditScore = Integer.valueOf(900);
	BigDecimal productWorth = BigDecimal.TEN;
	Auction auction = Mockito.mock(Auction.class);
	Buyer buyer = Mockito.mock(Buyer.class);
	Insurance expectedInsurance = new Insurance();

	Mockito.when(auctionRepositoryStub.findById(auctionId))
			.thenReturn(Optional.of(auction));
	Mockito.when(buyerRepositoryStub.findById(buyerId))
			.thenReturn(Optional.of(buyer));
	Mockito.when(auction.getProductWorth()).thenReturn(productWorth);
	Mockito.when(buyer.getCreditScore()).thenReturn(buyerCreditScore);
	Mockito.when(insuranceCalculatorStub.generateQuote(productWorth, buyerCreditScore))
			.thenReturn(expectedInsurance);

	// Act
	Insurance insurance = basicAuctionService.offerInsurance(auctionId, buyerId);

	// Assert
	assertThat(insurance).isSameAs(expectedInsurance);
}

In this test method, we are:

       (L3):  Configuring the BasicAuctionService for PowerMock to be able to intercept the creation of the InsuranceCalculator object. Take note here that we are not registering the InsuranceCalculator but the SUT class itself.

       (L16-18):  Informing PowerMock that when an InsuranceCalculator object is created with no arguments, then we should replace it with our insuranceCalculatorStub.

       (L34):  Making sure that for given productWorth and buyerCreditScore the insuranceCalculatorStub returns the expectedInsurance.

       (L41):  Asserting that the returned insurance is the one we expected.

 

Tip
Tip

 

3)     Powermock: mocking multi-param constructor

Most of the time, we will be dealing with objects that are created with a set of parameters being passed to their constructor. Let us go through a variation of the first example where we create a pre-configured InsuranceCalculator with a Category and Buyers creditScore:

 

SUT Code


public Insurance offerInsurancePerCategory(Integer auctionId, Integer buyerId) {
	Buyer buyer = buyerRepository.findById(buyerId).get();
	Auction auction = auctionRepository.findById(auctionId).get();

	InsuranceCalculator insuranceCalculator
			= new InsuranceCalculator(auction.getCategory(), buyer.getCreditScore());

	if(!buyer.hasInsuranceForAuction(auction)){
		return insuranceCalculator.generateQuote(auction.getProductWorth());
	}

	return null;
}

Now, let's take a look at the test method:

 

Test Code


@Test
public void shouldCalculateInsurancePerCategory() throws Exception{
	// Arrange
	Integer auctionId = Integer.valueOf(10);
	Integer buyerId = Integer.valueOf(4);
	Integer buyerCreditScore = Integer.valueOf(900);
	BigDecimal productWorth = BigDecimal.TEN;
	Category category = new Category();
	Auction auction = Mockito.mock(Auction.class);
	Buyer buyer = Mockito.mock(Buyer.class);
	Insurance expectedInsurance = new Insurance();

	PowerMockito.whenNew(InsuranceCalculator.class)
			.withArguments(category, buyerCreditScore)
			.thenReturn(insuranceCalculatorStub);

	Mockito.when(auctionRepositoryStub.findById(auctionId))
			.thenReturn(Optional.of(auction));
	Mockito.when(buyerRepositoryStub.findById(buyerId))
			.thenReturn(Optional.of(buyer));
	Mockito.when(auction.getProductWorth()).thenReturn(productWorth);
	Mockito.when(buyer.getCreditScore()).thenReturn(buyerCreditScore);
	Mockito.when(auction.getCategory()).thenReturn(category);
	Mockito.when(insuranceCalculatorStub.generateQuote(productWorth))
			.thenReturn(expectedInsurance);

	// Act
	Insurance insurance = basicAuctionService.offerInsurancePerCategory(auctionId, buyerId);

	// Assert
	assertThat(insurance).isSameAs(expectedInsurance);
}

Here we are:

       (L14-16):  Notifying PowerMock to replace the InsuranceCalculator with our insuranceCalculatorStub when it is created with category and buyerCreditScore parameters.

       (L25):  Making sure that insuranceCalculatorStub returns the expectedInsurance when invoking the generateQuote(productWorth)) method.

 

Note
Note

 

4)     Powermock: mocking constructor with wildcard input

Sometimes we simply do not care what the exact objects or values that are passed into a constructor are. We just want to make sure that the arguments are of specific type, that is it. This is how PowerMock provides this sort of wildcard implementation:

 

Test Code


PowerMockito.whenNew(InsuranceCalculator.class)
		.withParameterTypes(Category.class, Integer.class)
		.thenReturn(insuranceCalculatorStub);

 

Note
Note

 

6)     Conclusion

 

Summary

 

We have seen how we can replace any object created with a new operator with a relevant stub of ours. PowerMock makes it easy and flexible. Who knows maybe sometimes we can spot a consruction of something really great!

Need more insight?
Repository
Repository
Glossary
Glossary
Tags:
Reference
You may also like:
mocking-private-methods-with-powermock
Mocking private methods with PowerMock
mocking-final-methods-and-classes-with-powermock
Mocking final methods and classes with Powermock
mocking-static-methods-with-powermock
Mocking static methods with PowerMock
Comments
Be the first to comment.
Leave a comment