logo
Solutionunittest

Mockito dynamic answering

Views: 2816 Created: 2019-03-31 Read time: 5 minutes
Post Preview
Tags:

1)     Preface

Let's look at the typical mock set-up in the arrange phase of a test:

	
when(concreteValue).thenReturn(concreteObject);
when(any(String.class)).thenReturn(concreteObject);


In most cases that is enough. We just want to return a particular object when a concrete parameter is passed or when any kind of object is passed as long as it meets the type requirement.

Sometimes though, having a set-up only for a wildcard parameter or parameters is not enough. We would like to have a choice at runtime what instance of an object should be returned. We would like to be able to catch the input parameters and based on their values decide the outcome dynamically. Mockito.thenAnswer and Mockito.doAnswer methods allow us to meet such needs and neatly implement our dynamic expectations. 

What will you learn:

      Using answers with lambdas

      Extracting parameters from an invocation

      Typical use-case scenarios where answers can be implemented 

 

2)     Mockito thenAnswer and doAnswer interfaces

Before we dive into the examples lets first look at the Answer interface:

	
T answer(InvocationOnMock invocation) throws Throwable

       	

This interface can be simply treated as a Function<InvocationOnMock, T> functional interface.

Thanks to that we do not need to use anonymous classes but merely go for the lambda expressions inside the thenAnswer and doAnswer methods:

	
when(objectStub.getUser(id)).thenAnswer((id) -> {
    	return id > 0 ? user : null;
});


 

3)     Mockito thenAnswer use-case #1: accept any, return dynamically

The most common scenario is that we set-up our mocks method to accept any incoming parameters and implement the answer method to return the desired object based on the combination and values of these parameters.

3.1   First example

SUT Code

	
public Map<Integer, String> countAddressesForCities(List<String> cities){
    Map<Integer, String> addressCountByCity = new HashMap<>();

    for(String city: cities){
       List<Address> addresses = addressRepositoryStub.findForCity(city);

       String printReadyAddressCount = printService.prepareForPrint(addresses);
       printService.print(printReadyAddressCount);

       addressCountByCity.put(addresses.size(), city);
    }

    return addressCountByCity;
}

In this SUT method we:

      Here we are testing the countAddressesForCities method of the AddressesService.

      The method retrieves the number of addresses for each of the cities given and logs the count with an appropriate message. We want to make sure that for each city the message is printed correctly.

 

Test Code


@Mock
private AddressRepository addressRepositoryStub;
@Mock
private PrintService printServiceMock;

@InjectMocks
private AddressService addressServiceSUT;

private List<Address> parisAddresses; // contains 3 addresses
private List<Address> londonAddresses; // contains 5 addresses

@Test
public void shouldLogAddressesCountForCities() throws Exception{
    // Arrange
    when(addressRepositoryStub.findForCity("Paris")).thenReturn(parisAddresses);
    when(addressRepositoryStub.findForCity("London")).thenReturn(londonAddresses);

    when(printServiceMock.prepareForPrint(Mockito.any(List.class))).thenAnswer((invocation) -> {
			List<Address> addresses = (List<Address>) invocation.getArgument(0);

			return addresses.size() + " addresses found";
		});

    // Act
    addressServiceSUT.countAddressesForCities(Arrays.asList(new String[]{"Paris", "London"}));

    // Assert
    verify(printServiceMock, times(1)).print("3 addresses found");
    verify(printServiceMock, times(1)).print("5 addresses found");
}
	

In this test method we: 

      (L2,4): Inject addressRepositoryStub and printServiceMock into our addressServiceSUT.

      (L15,16): Prepare the addressRepositoryStub to return a predefined list of addresses depending on the passed city parameter.

      (L18-22): Depending on the size of the passed list prepare theprintSerivceMock. method to return adequate message.

      (L25): Invoke the method under test: addressServiceSUT.countAddressesForCities.

      (L28-29): Verify that a proper message has been printed for each of the cities passed in. 

Here the Answer feature allows us to return the desired message based on the input. 

3.2   Second Example

SUT Code

	
public Integer countAddressesOnHigherFloors(){
    List<Address> capitalAddresses = addressRepositoryStub.getCapitalAddresses();
    List<Address> nonCapitalAddresses = addressRepositoryStub.getNonCapitalAddresses();

    Integer higherFloorAddressesCount = 0;

    higherFloorAddressesCount += capitalAddresses.stream()
            .filter(predictaeService.getPredicate(capitalAddresses))
            .count();

    higherFloorAddressesCount += nonCapitalAddresses.stream()
            .filter(predictaeService.getPredicate(nonCapitalAddresses))
            .count();

    return higherFloorAddressesCount;
}


In this SUT method we are:

      (L2,3): Retrieving all the capital and non-capital addresses.

      (L7-13): Counting the addresses which can be considered as places on high floors.

      The way we determine, that is different for capital and non-capital addresses and is resolved by predicateService.getPredicate() method.

      (L15): Return the total count which combines capital and non-capital addresses. 

 

Test Code

	
@Mock
private AddressRepository addressRepositoryStub;
@Mock
private PredictaeService predictaeServiceStub;

@InjectMocks
private AddressService addressServiceSUT;

// contains 2 entries with floor > 4;
private List<Address> capitalAddresses;
// contains 1 entry with floor > 2;
private List<Address> nonCapitalAddresses;

@Test
public void shouldCountHighFloorAddresses() throws Exception{
    // Arrange
    when(addressRepositoryStub.getCapitalAddresses()).thenReturn(capitalAddresses);
    when(addressRepositoryStub.getNonCapitalAddresses()).thenReturn(nonCapitalAddresses);

    when(predictaeService.getPredicate(anyList())).thenAnswer((invocation) -> {
       List<Address> addresses = (List<Address>) invocation.getArguments()[0];
       Address firstAddress = addresses.get(0);

        Predicate<Address> capitalHighFloorPredicate = (address -> address.getFloor() > 4);
        Predicate<Address> nonCapitalHighFloorPredicate	= (address -> address.getFloor() > 2);

        if(firstAddress.isCapital()){
            return capitalHighFloorPredicate;
        }else{
            return nonCapitalHighFloorPredicate;
        }
    });

    // Act
    Integer higherFloorAddressesCount = addressServiceSUT.countAddressesOnHigherFloors();

    // Assert
    assertThat(higherFloorAddressesCount).isEqualTo(3);
}


In this test method we:

      (L2,4): Inject addressRepositoryStub and predicateServiceStub into our addressServiceSUT.

      (L15,16): Prepare the addressRepositoryStub to return predefined capitalAddresses and nonCapitalAddresses.

      (L18-30): Prepare the predicateServiceStub so that it returns a different predicate for the filter methods based on the content of the passed List.

Here if List contains capitals, the addresses are regarded as higher floor ones if the floor is 5 or greater. 2 or greater for if List contains non-capitals.

      (L33,36): We call the SUT method and assert the expected number of high floor addresses. 

As we see the Answer feature allows us to return the desired predicate dynamically based on the List content.

 

Note
Note

 

4)     Mockito thenAnswer use-case #2: subsequent invocations

Sometimes it's not the input parameters or their content that should determine the outcome, but simply the invocation sequence. 

Sometimes we would like to simply return a new object each time a method is called.

This might be particularly useful when a Stream is involved:

 

SUT Code

	
public void countOccupied(String city){
    Stream<Address> cityAdressesStream = addressRepository.findByCity(city);
    printService.print(cityAdressesStream.count() + " addresses in the city");

    Stream<Address> occupiedCityAdressesStream = addressRepository.findByCity(city)
                .filter((address) -> return address.isOccupied());
    printService.print(occupiedCityAdressesStream.count() + " addresses occupied");
}

	

In this SUT method, we:

      (L3,7): First count all the addresses in the given city (for comparison) and then counting only these which are occupied.

      (L2,3): The addressRepository.findByCity method returns a Stream<Address> based on the city parameter given. It is invoked twice to get a new Stream of Addresses because the cityAdressesStream.count() method closes the first stream.

 

Test Code

	
@Mock
private AddressRepository addressRepositoryStub;
@Mock
private PrintService printServiceMock;

@InjectMocks
private AddressService addressServiceSUT;

private List<Address> parisAddresses; // contains 3 addresses

@Test
public void shouldCountAddressesForCities() throws Exception{
    // Arrange
    when(addressRepositoryStub.findForCity("Paris").thenAnswer((invocation) -> {
		return Stream.of(parisAddresses);
    });

    // Act
    addressServiceSUT.countOccupied("Paris");

    // Assert
    Mockito.verify(printServiceMock, Mockito.times(1)).print("5 addresses in the city");
    Mockito.verify(printServiceMock, Mockito.times(1)).print("2 addresses occupied");
}
     	

In this test method we:

      (L2,4): We inject addressRepositoryStub and printServiceMock into our addressServiceSUT.

      (L14,15): Prepare the addressRepositoryStub to always return a fresh/new Stream of Addresses whenever the method is invoked with the Paris parameter.

      (L19):  Invoke the method under test: addressServiceSUT.filterOccupied.

      (L22,23): Verify that a proper message has been printed showing the total number of addresses for that city followed by the number of occupied ones.

 

Tip
Tip

 

5)     Mockito thenAnswer user-case #3: resetting your stubs

Another scenario is when we would like to dynamically control the return value for one of our stubs.

There is an initial set-up present but we would like to change it during the course of a test based on some dynamic scenario.

Let's take a look at an example from a Bid Auction House where we try to save the bid for an auction:

 

SUT Code


private class AuctionService{
    private AuctionRepository auctionRepository;
    private LoggingService loggingService;

    private static final Integer MAX_BID_ATTEMPTS = Integer.valueOf(5);

    public boolean bidOnAuction(Integer auctionId, Bid bid){
        Integer attemptCount = Integer.valueOf(0);
        boolean bidSuccessfull = false;

        while(attemptCount < MAX_BID_ATTEMPTS){
            try{
                auctionRepository.saveBidOnAuaction(auctionId, bid);
                bidSuccessfull = true;
                break;
            }catch(AuctionLockedException e){
                loggingService.logBidUnsuccessfull();
                attemptCount++;
            }

			wait();
        }

        return bidSuccessfull;
    }
}

In this SUT method we:

      (L13): We try to save the Bid for an auctionId.

      (L16-19): If another user is trying to bid at the same time we get an AuctionLockedException.

      (L17,21): We log the error and wait() a moment to retry. We have 5 attempts in total otherwise the whole action is unsuccessful.

 

Test Code


@InjectMocks
private AuctionService auctionServiceSUT;

@Mock
private LoggingService loggingServiceMock;

@Mock
private AuctionRepository auctionRepositoryStub;

@Test
public void shouldPlaceBidAfterUnsuccessfulAttempts() throws Exception{
    // Arrange
    Integer auctionId = Integer.valueOf(10);
    Bid bidDummy = new Bid();

    when(auctionRepositoryStub.saveBidOnAuaction(auctionId, bidDummy))
        .thenAnswer((invocation) -> {
            Integer numberOfUnsuccessfullAttempts = mockingDetails(loggingServiceMock)
                    .getInvocations()
                    .size();

            if(numberOfUnsuccessfullAttempts <= 2){
                throw new AuctionLockedException();
            }

            return true;
        });

    // Act
    boolean bidSuccessfull = auctionServiceSUT.bidOnAuction(auctionId, bidDummy);

    // Assert
    assertTrue(bidSuccessfull);
    verify(loggingServiceMock, times(2)).logBidUnsuccessfull();
}

In this test method we:

      (L16-27): We set-up the auctionRepositoryStub.saveBidOnAuction method to throw an AuctionLockedException for the first two attempts and return successfully on the third attempt.

      (L30): We invoke the auctionServiceSUT.bidOnAuction method on the SUT.

      (L33): We check that the bid has been successful.

      (L34): Additionally we verify that proper errors have been logged.

 

6     Mockito thenAnswer don’t: verifying the parameters

It is possible and tempting to capture and check the incoming parameters and make the assertion there and then inside the answer method.

	
when(myStub.invokeMethod(paramOne, paramTwo))
      .thenAnswer((invocation) -> {
          assertThat(paramOne).isEqualTo(expectedParamOne);
		  assertThat(paramTwo).isEqualTo(expectedParamTwo);

          return true;
      });
	

 

This is not the place and time during the test to perform such actions. The answer feature is meant to dynamically return a particular value based on the incoming parameters, that's it. The assertions are done later on in the test based on the value returned.

 

Note
Note

 

If we want to assert solely based on the parameters themselves, we should use the Mockito's @ArgumentCaptor feature.

 

7     Conclusion

 

Summary

 

We have seen that the Answer feature can be used in a variety of different situations. It allows the developer to act accordingly to cover the most intricate details of different scenarios that can occur for his implementation.

It should be noted though, that if this feature is used too often in our testing suite, then probably we need to go back to our implementation and start thinking about some kind of refactoring. Answer is an advanced tool and if seen too often can be one of the indicators of the codebase beginning to become too complicated.

Need more insight?
Repository
Repository
Glossary
Glossary
Tags:
Reference
You may also like:
mockito-stubbing
Mockito stubbing
mockito-argument-capturing
Mockito argument capturing
unit-testing-common-mistakes
Unit testing common mistakes
Comments
Be the first to comment.
Leave a comment