Creating partial mocks

Using partial mocks generally should be considered a code smell. When writing good and clean code, you want it to be modular and follow all of the best practices, including the SOLID principles (please refer to the Introduction section of Chapter 2, Creating Mocks, for an elaborate explanation). When working with complex code, as a refactoring process, one tries to split the largest tasks into more modular ones. During that process, you may want to mock external dependencies of the system under test. You might come across a situation in which you do not want to mock the entire dependency but only a part of it while leaving the rest unstubbed. Such a mocked class is called a partial mock and creating one means that a class that you are mocking most likely does more than one thing, which is a pure violation of the single-responsibility principle.

Let's consider the example from the current chapter – the TaxService class. It has two responsibilities:

  • Calculation of the tax factor for a person
  • Updating the tax data for the person via a web service

The class both computes and updates data, so all-in-all it's not responsible for a single action, but two. If we split these responsibilities into two classes, since we don't want to really call a web service, we could create a mock only for the class responsible for updating the tax data. At the end of the day, that's how we will eliminate the need for creating a partial mock.

So why would you want to create a partial mock? You definitely would not need to do it in your new code base (because you are following the SOLID principles). However, there are cases where you will want to create partial mocks: when dealing with legacy code or third-party libraries. This would be code that you can't easily change or you can't change at all.

In this recipe, we will describe how to create partial mocks, but before that, let's have another look at the concept of a test double known as stub (based on Gerard Meszaros's definitions from the xUnit patterns). Stub is an object that has predefined answers to method executions made during the test.

The process of predefining those answers is called stubbing (refer to Chapter 4, Stubbing Behavior of Mocks, and Chapter 5, Stubbing Behavior of Spies, for more details), and you have already seen it being used with the following syntax throughout the book (an example for stubbing a method that returns a value using first the BDDMockito and then the Mockito syntax):

BDDMockito.given(...).willReturn(...)
Mockito.when(...).thenReturn(...)

Getting ready

For this recipe, we will reuse the example from the previous recipe, but let's take another look at it. Our system under test for this recipe will be a TaxFactorProcessor class that interacts with a TaxService class in order to calculate the tax factor and update the tax data of a given person. 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...

In order to create a partial mock, you need to perform the following steps:

  1. Create a mock (either via code or annotations).
  2. Stub the method execution so that it calls a real method (we want to execute the real logic).

The following tests illustrate the case for JUnit. For a TestNG configuration, please refer to Chapter 1, Getting Started with Mockito, (remember that I'm using the BDDMockito.given(...) and AssertJ's BDDAssertions.then(...) static methods. 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:

@RunWith(MockitoJUnitRunner.class)
public class TaxFactorProcessorTest {

    @Mock TaxService taxService;

    @InjectMocks TaxFactorProcessor systemUnderTest;

    @Test
    public void should_return_default_tax_factor_for_person_from_undefined_country() {
        // given
        given(taxService.calculateTaxFactorFor(any(Person.class))).willCallRealMethod();

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

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

}

What happens in the test is that we create a mock and inject it to our system under test via annotations. Next, we are stubbing the mock's calculateTaxFactorFor(...) method execution in the test itself so that it calls a real method of the TaxService class. The rest of the test is self-explanatory. First, we execute the system under a test method and then we assert that the behavior of the system is the same as we would expect it.

How it works...

The explanation of the internals related to stubbing the mocked object with the execution of a real implementation is covered in more depth in the next chapter, related to stubbing.

There's more...

You can make the mock call real methods by default by changing its default answer (check the corresponding recipe in Chapter 2, Creating Mocks, for more details), as presented in the following snippet:

@Mock(answer = Answers.CALLS_REAL_METHODS) TaxService taxService;

However, because each method will by default call a real method right now, coming back to our example, we need to stub the execution of the method that calls a web service so that it doesn't do it and the rest of the test remains the same. Have a look at the following code:

// given
doNothing().when(taxService).updateTaxData(anyDouble(), any(Person.class));

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.21.98.207