Creating spies in code

In the following recipe, we will learn how to create a spy using Mockito code only (without annotations). As a reminder, you should have very legitimate reasons to use a spy in your code, otherwise it most likely signifies that there is something wrong with your code's design.

Getting ready

Our system under test for this recipe will be TaxFactorProcessor, which interacts with TaxService. Let's assume that the latter is part of some legacy system that, for the time being, you don't want to refactor. Also assume that TaxService does two things. First, it performs calculations of a tax factor, and second it sends a request via a web service to update tax data for a given person. If the person's country is not specified explicitly (and by default it's not in our examples), then a default tax factor value is returned from TaxService. In a proper code base, one should separate these two functionalities (calculation and data update) into separate classes, but for the sake of this example, let's leave it as it is. Have a look at the following code:

public class TaxFactorProcessor {

    public static final double INVALID_TAX_FACTOR = -1;

    private final TaxService taxService;

    public TaxFactorProcessor(TaxService taxService) {
        this.taxService = taxService;
    }

    public double processTaxFactorFor(Person person) {
        try {
            double taxFactor = taxService.calculateTaxFactorFor(person);
            taxService.updateTaxData(taxFactor, person);
            return taxFactor;
        } catch (Exception e) {
            System.err.printf("Exception [%s] occurred while trying to calculate tax factor for person[%s]%n", e, person.getName());
            return INVALID_TAX_FACTOR;
        }
    }

}

We will test our system to check that TaxService performs computations but does not call a web service in our unit test.

How to do it...

To create a spy of a given object using the Mockito API, you need to call the static Mockito.spy(T object) method with the instantiated object for which you want to create a spy.

The following test is written for JUnit. For a TestNG configuration, please refer to Chapter 1, Getting Started with Mockito (remember that I'm using the AssertJ's BDDAssertions.then(...) static method. Refer to Chapter 7, Verifying Behavior with Object Matchers, to know how to work with AssertJ or how to do the same with Hamcrest's assertThat(...)). Have a look at the following code:

public class TaxFactorProcessorTest {

    TaxService taxService = spy(new TaxService());

    TaxFactorProcessor systemUnderTest = new TaxFactorProcessor(taxService);

    @Test
    public void should_return_default_tax_factor_for_person_from_undefined_country() {
        // given
        doNothing().when(taxService).updateTaxData(anyDouble(), any(Person.class));

        // when
        double taxFactor = systemUnderTest.processTaxFactorFor(new Person());

        // then
        then(taxFactor).isEqualTo(TaxService.DEFAULT_TAX_FACTOR);
    }

}

What happens in the test is that we first create a spy for the TaxService instance (via the statically imported Mockito.spy(...) method), and next we create the system under test. In the body of our test, in the //given section, we are stubbing our spy so that it does nothing when the updateTaxData(...) method is called (don't worry if you haven't seen the stubbing syntax of spies before. You can read more about it in Chapter 5, Stubbing Behavior of Spies). In the //when section, we are executing the application logic, and in the //then part, we are verifying whether the processed tax factor is the default one from the application.

How it works...

Mockito internally runs the following when you execute the static spy method:

 public static <T> T spy(T object) {
        return mock((Class<T>) object.getClass(), withSettings()
                .spiedInstance(object)
                .defaultAnswer(CALLS_REAL_METHODS));
    }

You can see that a spy is in fact a mock that by default calls real methods. Additionally, the MockitoSpy interface is added to that mock.

There are some gotchas regarding spy initialization with Mockito. Mockito creates a shallow copy of the original object so that tested code won't see or use the original object. That's important to know since any interactions on the original object will not get reflected on the spy, and vice versa (if you want to interact directly with the original object, you need to use the AdditionalAnswers.delegateTo(...) answer. To check how to stub methods with a custom answer, check Chapter 4, Stubbing Behavior of Mocks, or Chapter 5, Stubbing Behavior of Spies, for an explanation of a mock or spy's method stubbing.)

Another issue is final methods. Mockito can't stub final methods, so when you try to stub them, you will not even see a warning message and a real implementation will be called. Refer to Chapter 5, Stubbing Behavior of Spies, for more information on this. PowerMock related recipes to see how to deal with those methods (remember that using PowerMock suggests that there is most likely something really wrong with your code base, so you should use it with extreme caution).

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.135.36