May 8, 2020
Estimated Post Reading Time ~

Unit testing with Mockito

A unit is the smallest testable part of an application. Mockito is a well known mock framework that allows us to create configure mock objects. With Mockito we can mock both interfaces and classes in the class under test. Mockito also helps us to reduce the number of boilerplate code while using mockito annotations.

Adding Mockito to the project

using gradle
testCompile "org.mockito:mockito−core:2.7.7"

using maven
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>    <version>2.7.7</version>    <scope>test</scope>
</dependency>


Mockito annotations
@Mock – used for mock creation.
@Spy – creates a spy object.
@InjectMocks – instantiates the tested object and injects all the annotated field dependencies into it
@Captor – used to capture argument values for further assertions

Mockito example @Mock
Let’s say we have the following classes and we want to write a test for the CalculationService:

public class CalculationService {
 
   private AddService addService;
   
   public int calculate(int x, int y) {
       return addService.add(x, y);
   }
}
 
public class AddService {
 
   public int add(int x, int y) {
       return x+y;
   }
}

The usage of the @Mock and @InjectMock annotations is shown in the following sample code:

@InjectMocks
private CalculationService calculationService;
 
@Mock
private AddService addService;
 
@Before
public void setUp() {
   // initializes objects annotated with @Mock, @Spy, @Captor, or @InjectMocks
   MockitoAnnotations.initMocks(this);
}
 
@Test
public void testCalculationService() {
    // mock the result from method add in addService
    doReturn(20).when(addService).add(10, 10);
 
    // verify that the calculate method from calculationService will return the same value
    assertEquals(20, calculationService.calculate(10, 10));
}

@Spy
Mockito spy is used to spying on a real object. The main difference between a spy and mock is that with spy the tested instance will behave as a normal instance. The following example will explain it:

@Test
public void testSpyInstance() {
    List<String> spyList = spy(new ArrayList());
    spyList.add("firstElement");
    spyList.add("secondElement");
    verify(spyList).add("firstElement");
    verify(spyList).add("secondElement");
 
    assertEquals(2, spyList.size());
}

Note that method add is called and the size of the spy list is 2.
@Captor
Mockito framework gives us plenty of useful annotations. One of the most recent that I’ve had a chance to use is @Captor. ArgumentCaptor is used to capture the inner data in a method that is either void or returns a different type of object.
Let’s say we have the following method snippet:

public class AnyClass {
    public void doSearch(SearchData searchData) {
        CustomData data = new CustomData("custom data");
        searchData.doSomething(data);
    }
}

We want to capture the argument data so we can verify its inner data. So, to check that, we can use ArgumentCaptor from Mockito:

// Create a mock of the SearchData
SearchData data = mock(SearchData.class);

// Run the doSearch method with the mock
new AnyClass().doSearch(data);

// Capture the argument of the doSomething function
ArgumentCaptor<CustomData> captor = ArgumentCaptor.forClass(CustomData.class);
verify(data, times(1)).doSomething(captor.capture());

// Assert the argument
CustomData actualData = captor.getValue();
assertEquals("custom data", actualData.customData);


New features in Mockito 2.x
Since its inception, Mockito lacked mocking finals. One of the major features in the 2.X version is the support stubbing of the final method and final class. This feature has to be explicitly activated by creating the file MockMaker in this directory src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker containing a single line:
mock-maker-inline

public final class MyFinalClass {
 
    public String hello() {
        return "my final class says hello";
    }
}
 
public class MyCallingClass {
 
    final MyFinalClass myFinalClass = new MyFinalClass();
 
    public String executeFinal() {
        return myFinalClass.hello();
    }
}
 
public class MyCallingClassTest {
 
    @Test
    public void testFinalClass() {
        MyCallingClass myCallingClass = new MyCallingClass();
        MyFinalClass myFinalClass = mock(MyFinalClass.java);
 
        when(myFinalClass.hello()).thenReturn("testString");
 
        assertEquals("testString", myCallingClass.executeFinal());
    }
}

Given the following example, without the file org.mockito.plugins.MockMaker and its content, we get the following error:

When the file is in the resources and the content is valid, we are all good.

The plan for the future is to have a programmatic way of using this feature.

Conclusion
In this article, I gave a brief overview of some of the features in the Mockito test framework. Like any other tool, it must be used in a proper way to be useful. Now go and bring your unit tests to the next level.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.