4 min read

Reducing boilerplate code with annotations

Mockito allows the use of annotations to reduce the lines of test code in order to increase the readability of tests. Let’s take into consideration some of the tests that we have seen in previous examples.

Removing boilerplate code by using the MockitoJUnitRunner

The shouldCalculateTotalWaitingTimeAndAssertTheArgumentsOnMockUsingArgumentCaptor from Verifying behavior (including argument capturing, verifying call order and working with asynchronous code) section, can be rewritten as follows, using Mockito annotations, and the @RunWith(MockitoJUnitRunner.class) JUnit runner:

@RunWith(MockitoJUnitRunner.class) public class _07ReduceBoilerplateCodeWithAnnotationsWithRunner { @Mock KitchenService kitchenServiceMock; @Captor ArgumentCaptor mealArgumentCaptor; @InjectMocks WaiterImpl objectUnderTest; @Test public void shouldCalculateTotalWaitingTimeAndAssert TheArgumentsOnMockUsingArgumentCaptor() throws Exception { //given final int mealPreparationTime = 10; when(kitchenServiceMock.calculate PreparationTime(any(Meal.class))).thenReturn(mealPreparationTime); //when int waitingTime = objectUnderTest.calculate TotalWaitingTime(createSampleMeals ContainingVegetarianFirstCourse()); //then assertThat(waitingTime, is(mealPreparationTime)); verify(kitchenServiceMock).calculatePreparation Time(mealArgumentCaptor.capture()); assertThat(mealArgumentCaptor.getValue(), is (VegetarianFirstCourse.class)); assertThat(mealArgumentCaptor.getAllValues().size(), is(1)); } private List createSampleMeals ContainingVegetarianFirstCourse() { List meals = new ArrayList(); meals.add(new VegetarianFirstCourse()); return meals; } }


What happened here is that:

  • All of the boilerplate code can be removed due to the fact that you are using the @RunWith(MockitoJUnitRunner.class) JUnit runner
  • Mockito.mock(…) has been replaced with @Mock annotation

    You can provide additional parameters to the annotation, such as name, answer or extraInterfaces.

    The fieldname related to the annotated mock is referred to in any verification so it’s easier to identify the mock

  • ArgumentCaptor.forClass(…) is replaced with @Captor annotation.

    When using the @Captor annotation you avoid warnings related to complex generic types

  • Thanks to the @InjectMocks annotation your object under test is initialized, proper constructor/setters are found and Mockito injects the appropriate mocks for you

    There is no explicit creation of the object under test

    You don’t need to provide the mocks as arguments of the constructor

    Mockito @InjectMocksuses either constructor injection, property injection or setter injection

Taking advantage of advanced mocks configuration

Mockito gives you a possibility of providing different answers for your mocks. Let’s focus more on that.

Getting more information on NullPointerException

Remember the Waiter’s askTheCleaningServiceToCleanTheRestaurantMethod():

@Override public boolean askTheCleaningServiceToCleanTheRestaurant (TypeOfCleaningService typeOfCleaningService) { CleaningService cleaningService = cleaningServiceFactory.getMe ACleaningService(typeOfCleaningService); try{ cleaningService.cleanTheTables(); cleaningService.sendInformationAfterCleaning(); return SUCCESSFULLY_CLEANED_THE_RESTAURANT; }catch(CommunicationException communicationException){ System.err.println(“An exception took place while trying to send info about cleaning the restaurant”); return FAILED_TO_CLEAN_THE_RESTAURANT; } }


Let’s assume that we want to test this function. We inject the CleaningServiceFactory as a mock but we forgot to stub the getMeACleaningService(…) method. Normally we would get a NullPointerException since, if the method is not stubbed, it will return null. But what will happen, if as an answer we would provide a RETURNS_SMART_NULLS answer? Let’s take a look at the body of the following test:

@Mock(answer = Answers.RETURNS_SMART_NULLS) CleaningServiceFactory cleaningServiceFactory; @InjectMocks WaiterImpl objectUnderTest; @Test public void shouldThrowSmartNullPointerExceptionWhenUsingUnstubbedMethod() { //given // Oops forgotten to stub the CleaningServiceFactory.getMeACle aningService(TypeOfCleaningService) method try { //when objectUnderTest.askTheCleaningServiceToCleanTheRestaurant( TypeOfCleaningService.VERY_FAST); fail(); } catch (SmartNullPointerException smartNullPointerException) { System.err.println(“A SmartNullPointerException will be thrown here with a very precise information about the error [” + smartNullPointerException + “]”); } }


What happened in the test is that:

  • We create a mock with an answer RETURNS_SMART_NULLS of the CleaningServiceFactory
  • The mock is injected to the WaiterImpl
  • We do not stub the getMeACleaningService(…) of the CleaningServiceFactory
  • The SmartNullPointerException will be thrown at the line containing the cleaningService.cleanTheTables()
  • It will contain very detailed information about the reason for the exception to happen and where it happened

In order to have the RETURNS_SMART_NULLS as the default answer (you wouldn’t have to explicitly define the answer for your mock), you would have to create the class named MockitoConfiguration in a package org.mockito.configuration that either extends the DefaultMockitoConfiguration or implements the IMockitoConfiguration interface:

package org.mockito.configuration; import org.mockito.internal.stubbing.defaultanswers.ReturnsSmartNulls; import org.mockito.stubbing.Answer; public class MockitoConfiguration extends DefaultMockitoConfiguration { public Answer<Object> getDefaultAnswer() { return new ReturnsSmartNulls(); } }


Summary

In this article we learned in detail about reducing the boilerplate code with annotations, and taking advantage of advanced mocks configuration, along with their code implementation.

Resources for Article :


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here