logo
Solutionunittest

Spying with Mockito

Views: 4098 Created: 2019-02-09 Read time: 3 minutes
Post Preview
Tags:

1     Preface

The spying feature in Mockito is yet another powerful weapon in your unit testing arsenal.

Unlike the base mocking functionality, it is tracking and allows the set-up of a real object.

That said, all the calls are invoking real methods, unless configured differently.

There are two ways we can create a spy:

Using @Spy annotation


@Spy
private AuctionService auctionServiceSpy;

@BeforeEach
private void init(){
	MockitoAnnotation.initMocks(this);
}

Using Mockito.spy method:


private AuctionService auctionServiceSpy;

@BeforeEach
private void init(){
	auctionServiceSpy = spy(new AuctionService());
}

2)     Mockito spying is for the SUT exclusive

As a general rule of unit testing, we are supposed to test the SUT solely and to be completely independent of any other code.

That is why the dependencies should be mocked.

 

Note
Note

 

When we are spying on a dependency, then we are allowing an additional code outside of the SUT to be involved in the result of our test.

A change to the implementation of such a dependency may have an effect on our code, while the SUT itself did not change. That is not the perfect scenario.

That is why we should only aim to use the spying feature on the SUT itself when it is absolutely necessary. Read on for more details.

3)     doXXX.when vs when.thenXXX

In order to set-up a mocked class we usually use the when(mock.callMethod()).thenReturn() semantics.

This will not work as intended when dealing with a spy. 

Let's look at an example:


	AuctionService auctionServiceSpy = spy(new AuctionService());
	when(auctionServiceSpy.getLatestAuctions()).thenReturn(dummyList);

Here the spy.callMethod real implementation will be called during the set-up. We want to make it return a configured value

and not to invoke the particular method and most likely mess up our test case.

That is why we need to invert syntax when we first define the value to be returned and then pass the spy method.

Thanks to that the framework is ready, and knows that it must watch out not to invoke by accident a real method if a spy if passed.


	AuctionService auctionServiceSpy = spy(new AuctionService());
	doReturn(dummyList).when(auctionServiceSpy).getLatestAuctions();

 

4)     Mockito spying and argument wildcards

Similar to a mock interaction, we can use all the available Mockito wildcards to correctly configure our spy:


	AuctionService auctionServiceSpy = spy(new AuctionService());

	doReturn(auction).when(auctionServiceSpy).findByTitle(anyString());

	doThrow(new AuctionLockedException()).when(
		auctionServiceSpy).bid(anyInt(), any(Bid.class));

	doThrow(new AuctionLockedException()).when(
		auctionServiceSpy).bid(eq(auctionId), any(Bid.class));

	doNothing().when(auctionServiceSpy).closeAuction(eq(auction), any(Status.class));

5)     Using Mockito spying in order to mock static methods

In a well designed and coded application there really aren't many use cases for a spy.

These kind of applications, as we all know it, are rare.

Let's look at the first example of a typical spying use case where we have that problematic usage of a static method that we cannot mock.

SUT Code


@Service
public class BasicAuctionService implements AuctionService {

	@Autowired
	private SellerRepository sellerRepository;

	@Override
	@Transactional
	public void rateSeller(Integer sellerId, BigDecimal rating) {
		Seller seller = sellerRepository.getOne(sellerId);
		List<Rating> sellerRatings = seller.getRatings();
		sellerRatings.add(rating);

		BigDecimal newAverageRating = RatingUtils.calculateRating(sellerRatings);

		seller.setAverageRating(newAverageRating);
	}

public final class RatingUtils {

	public static BigDecimal calculateRating(List<Rating> ratings){
		final Integer roundingStrategy = Integer.valueOf(
				getApplicationContext().getEnvironment().getProperty("ROUNDING_STRATEGY"));

		return ratings.stream()
				.map(Rating::getRating)
				.reduce(BigDecimal.ZERO, BigDecimal::add)
				.divide(BigDecimal.valueOf(ratings.size()), roundingStrategy);
	}

In this SUT we:

      (L12):  Retrieve the seller's ratings and add the new rating.

      (L15):  Recalculate the average rating calling a static calculateRating method of a util class.

      (L23):  We statically try to retrieve a rounding strategy from an application context.

The problem here is that we cannot use the calculateRating method as it is because of the application context logic would cause

exception during our test and also we cannot mock the RatingUtils class as it is final and the calculateRating method is static on top of that.

We a little bit of refactoring and spying we can manage to make this code testable:

Refactored Code


@Override
@Transactional
public void rateSeller(Integer sellerId, Double rating) {
    Seller seller = sellerRepository.getOne(sellerId);
    List<Rating> sellerRatings = seller.getRatings();

    BigDecimal newAverageRating = calculateRating(sellerRatings);

    seller.setAverageRating(newAverageRating);
}

BigDecimal calculateRating(List<Rating> ratings){
	return RatingUtils.calculateRating(ratings);
}

In this refactored SUT we:

      (L13-15):  Moved the RatingUtils.calculateRating static call into a package level method.

      (L8):  We are calculating the average now with the use of this instance method.

Test Code


@InjectMocks
@Spy
private BasicAuctionService basicAuctionServiceSpy;

@Mock
private SellerRepository sellerRepository;

@Test
public void shouldRecalculateAverageRating() throws Exception{
    // Arrange
    Integer sellerId = Integer.valueOf(1);
    List<Rating> ratings = new ArrayList<>();
    Seller seller = new Seller();
    seller.setRatings(ratings);
    Rating newRating = new Rating();
    BigDecimal newRatingAverage = BigDecimal.valueOf(9.5);

    when(sellerRepository.getOne(sellerId)).thenReturn(seller);

    doReturn(newRatingAverage).when(basicAuctionServiceSpy).calculateRating(ratings);

    // Act
    basicAuctionServiceSpy.rateSeller(sellerId, newRating);

    // Assert
    assertThat(seller.getAverageRating()).isEqualTo(newRatingAverage);
}

In this test method we:

      (L12-17):  Prepare basic values and objects.

      (L21):  Make our spied SUT return newRatingAverage when the calculateRating method is called.

      (L24):  Call the SUT rateSeller method.

      (L27):  Verify that the seller has been updated with the expected newRatingAverage.

 

Tip
Tip

 

6)     Using Mockito spying to verify anonymous or inner class method invocations

Another use case for a spy is when a SUT is using an inner class, and we want to make sure, that one or a group of its methods were called.

A similar strategy would apply to an anonymous class.

SUT Code


public class OuterClass {
    String toPrint = "HelloThere";

    public void process() {

        class InnerClass {
            public void innerMethod() {
                System.out.println(toPrint);
            }
        }

		// some logic

        (new InnerClass()).innerMethod();
    }
}

In this SUT method we are:

      (L7):  Create an inner class with myInnerMethod that prints out some logs.

      (L15):  Create an instance of the class and call its method.

The problem with testing that a certain message has been printed out is that the instance of the Inner is created inside the myMethod(). We cannot mock it and verify the behaviour in that case. Let's refactor this a bit:

Refactored Code


public class OuterClass {
    String toPrint = "Hello There";

    public void process() {

        class InnerClass {
            public void innerMethod() {
                Outer.this.printHello(toPrint);
            }
        }

        // some code

        (new InnerClass()).innerMethod();
    }

    void printHello(String toPrint){
		System.out.println(toPrint);
	}
}

In this refactored SUT version we:

      (L18-20):  Move the content of the innerMethod to a package level printHello method.

      (L9):  Call the Outer.printHello method

Thanks to that we can mock the printHello method and verify that it has been called with expected arguments:

Test Code


@Spy
private OuterClass outerSpy;

public void test() {
    // Arrange
    doNothing().when(outerSpy).printHello(anyString());

    // Act
    outer.process();

    // Assert
    verify(outerSpy).printHello("Hello There");
}

In this test method we:

      (L3):  Spy on the OuterClass.  

      (L10):  Call the SUT method

      (L13):  Verify that the innerMethod has printed out an expected message

7     Conclusion

Summary

 

We have seen that the spy is literally the real deal in the Mockito framework and allows to configure the not-so-standard situations.

We have to be cautious though, as a large number of spies in our test suite may be an indicator of the SUT being too complicated.

Ideally, we would like to see the usage of mocks wherever possible.

 

Need more insight?
Repository
Repository
Glossary
Glossary
Tags:
Reference
You may also like:
mockito-stubbing
Mockito stubbing
mockito-most-common-exceptions
Mockito most common exceptions
mockito-settings
Mockito settings
Comments
Be the first to comment.
Leave a comment