×
Community Blog Java Unit Testing Skill - PowerMock

Java Unit Testing Skill - PowerMock

This article summarizes several Java unit testing techniques.

1

Preface

When talking about methodology, Xiang, a technology expert of AMAP said, "Complicated problems should be simplified while the simple ones should be deepened."

This statement deeply touched me, and I believe it also applies to writing code. In other words, after splitting a complex logic into lots of simple logic, and then implementing each simple logic in-depth, the simple logic is integrated into complex logic. The quote above can be summed up as, "changing complexity into simplicity, from simplicity to complexity."

Writing Java unit testing cases simplifies complex problems, breaking down a complex piece of code into a series of simple unit testing cases. Writing Java unit testing cases lessens simple problems by learning a set of methods, summarizing a set of patterns, and applying it to the practice. Some Java unit testing techniques have been summarized below based on my work experience for communication and study purposes.

1. Environment Preparation

PowerMock is a more powerful framework that extends other mock frameworks, such as EasyMock. PowerMock uses a custom class loader and bytecode manipulation to simulate methods, including static methods, constructors, class's methods final, and private methods. Remove static initializers can be simulated as well.

1.1 Introduce the PowerMock Package

Add the following maven dependency to the pom.xml file to introduce the PowerMock package:

2

1.2 Integrate the SpringMVC Project

In the SpringMVC project, add the following maven dependency of JUnit to the pom.xml file:

3

1.3 Integrate the SpringBoot Project

In the SpringBoot project, add the following maven dependency of the JUnit to the pom.xml file:

4

1.4 A Simple Testing Case

Here, take the list below as an example to simulate a non-existent list. The returned list size is 100.

public class ListTest {
    @Test
    public void testSize() {
        Integer expected = 100;
        List list = PowerMockito.mock(List.class);
        PowerMockito.when(list.size()).thenReturn(expected);
        Integer actual = list.size();
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

2. Mock Statements

2.1 Mock Methods

Statement:

T PowerMockito.mock(Class clazz)

Purpose:

It can be used to simulate an object instance of a specified class.

When simulating a non-final method of a non-final class, such as interface, common class, and virtual base class, users do not need to use @RunWith and @PrepareForTest. When simulating the final class or final method, users must @RunWith and @PrepareForTest.

The forms of the annotations above are:

@RunWith(PowerMockRunner.class)

@PrepareForTest({TargetClass.class})

2.1.1 Simulate Common Methods of Non-Final Class

@Getter
@Setter
@ToString
public class Rectangle implements Sharp {
    private double width;
    private double height;
    @Override
    public double getArea() {
        return width * height;
    }
}

public class RectangleTest {
    @Test
    public void testGetArea() {
        double expectArea = 100.0D;
        Rectangle rectangle = PowerMockito.mock(Rectangle.class);
        PowerMockito.when(rectangle.getArea()).thenReturn(expectArea);
        double actualArea = rectangle.getArea();
        Assert.assertEquals("Return values are not equal", expectArea, actualArea, 1E-6D);
    }
}

2.1.2 Simulate Final Class or Final Method

@Getter
@Setter
@ToString
public final class Circle {
    private double radius;
    public double getArea() {
        return Math.PI * Math.pow(radius, 2);
    }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest({Circle.class})
public class CircleTest {
    @Test
    public void testGetArea() {
        double expectArea = 3.14D;
        Circle circle = PowerMockito.mock(Circle.class);
        PowerMockito.when(circle.getArea()).thenReturn(expectArea);
        double actualArea = circle.getArea();
        Assert.assertEquals("Return values are not equal", expectArea, actualArea, 1E-6D);
    }
}

2.2 MockStatic Method

Statement:

PowerMockito.mockStatic(Class clazz)

Purpose:

It can be used for static methods under simulate classes. Users must use @RunWith and @PrepareForTest.

@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {
    @Test
    public void testIsEmpty() {
        String string = "abc";
        boolean expected = true;
        PowerMockito.mockStatic(StringUtils.class);
        PowerMockito.when(StringUtils.isEmpty(string)).thenReturn(expected);
        boolean actual = StringUtils.isEmpty(string);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

3. Spy Statements

For an object, if users only want to simulate some of its methods and the other methods to be the same as the original, they can use the PowerMockito.spy instead of the PowerMockito.mock. Methods that have been set by the when the statement are called simulation methods, while those without the when statement are called original methods.

3.1 Spy Class

Statement:

PowerMockito.spy(Class clazz)

Purpose:

It can be used for partial methods of the simulation class.

Case:

public class StringUtils {
    public static boolean isNotEmpty(final CharSequence cs) {
        return !isEmpty(cs);
    }
    public static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {
    @Test
    public void testIsNotEmpty() {
        String string = null;
        boolean expected = true;
        PowerMockito.spy(StringUtils.class);
        PowerMockito.when(StringUtils.isEmpty(string)).thenReturn(!expected);
        boolean actual = StringUtils.isNotEmpty(string);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

3.2 Spy Objects

Statement:

T PowerMockito.spy(T object)

Purpose:

It can be used for partial methods of mock objects.

Case:

public class UserService {
    private Long superUserId;
    public boolean isNotSuperUser(Long userId) {
        return !isSuperUser(userId);
    }
    public boolean isSuperUser(Long userId) {
        return Objects.equals(userId, superUserId);
    }
}

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Test
    public void testIsNotSuperUser() {
        Long userId = 1L;
        boolean expected = false;
        UserService userService = PowerMockito.spy(new UserService());
        PowerMockito.when(userService.isSuperUser(userId)).thenReturn(!expected);
        boolean actual = userService.isNotSuperUser(userId);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4. When Statements

4.1 When(). Thirreturn () Mode

Statement:

PowerMockito.when(mockObject.someMethod(someArgs)).thenReturn(expectedValue) ;

PowerMockito.when(mockObject.someMethod(someArgs)).thenThrow(expectedThrowable) ;

PowerMockito.when(mockObject.someMethod(someArgs)).thenAnswer(expectedAnswer) ;

PowerMockito.when(mockObject.someMethod(someArgs)).thenCallRealMethod() .

Purpose:

It can be used to mock objects methods. Execute the original method first. Then, return the expected value, exception, response, or call real methods.

4.1.1 Return Expected Value

public class ListTest {
    @Test
    public void testGet() {
        int index = 0;
        Integer expected = 100;
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.when(mockList.get(index)).thenReturn(expected);
        Integer actual = mockList.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4.1.2 Return Expected Exception

public class ListTest {
    @Test(expected = IndexOutOfBoundsException.class)
    public void testGet() {
        int index = -1;
        Integer expected = 100;
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.when(mockList.get(index)).thenThrow(new IndexOutOfBoundsException());
        Integer actual = mockList.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4.1.3 Return Expected Response

public class ListTest {
    @Test
    public void testGet() {
        int index = 1;
        Integer expected = 100;
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.when(mockList.get(index)).thenAnswer(invocation -> {
            Integer value = invocation.getArgument(0);
            return value * 100;
        });
        Integer actual = mockList.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4.1.4 Call Real Methods

public class ListTest {
    @Test
    public void testGet() {
        int index = 0;
        Integer expected = 100;
        List<Integer> oldList = new ArrayList<>();
        oldList.add(expected);
        List<Integer> spylist = PowerMockito.spy(oldList);
        PowerMockito.when(spylist.get(index)).thenCallRealMethod();
        Integer actual = spylist.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4.2 DoReturn().when() Mode

Statement:

PowerMockito.doReturn(expectedValue).when(mockObject).someMethod(someArgs) ;

PowerMockito.doThrow(expectedThrowable).when(mockObject).someMethod(someArgs) ;

PowerMockito.doAnswer(expectedAnswer).when(mockObject).someMethod(someArgs) ;

PowerMockito.doNothing().when(mockObject).someMethod(someArgs) ;

PowerMockito.doCallRealMethod().when(mockObject).someMethod(someArgs) .

Purpose:

It can be used to mock objects methods. Return the expected value, exception, response, or call real methods without executing the original method

Note:

Do not use the following syntax:

PowerMockito.doReturn(expectedValue).when(mockObject.someMethod(someArgs)) ;

PowerMockito.doThrow(expectedThrowable).when(mockObject.someMethod(someArgs)) ;

PowerMockito.doAnswer(expectedAnswer).when(mockObject.someMethod(someArgs)) ;

PowerMockito.doNothing().when(mockObject.someMethod(someArgs)) ;

PowerMockito.doCallRealMethod().when(mockObject.someMethod(someArgs)) .

Although no compilation errors will occur, the UnfinishedStubbingException will occur during execution.

4.2.1 Return Expected Value

public class ListTest {
    @Test
    public void testGet() {
        int index = 0;
        Integer expected = 100;
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.doReturn(expected).when(mockList).get(index);
        Integer actual = mockList.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4.2.2 Return Expected Exception

public class ListTest {
    @Test(expected = IndexOutOfBoundsException.class)
    public void testGet() {
        int index = -1;
        Integer expected = 100;
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.doThrow(new IndexOutOfBoundsException()).when(mockList).get(index);
        Integer actual = mockList.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4.2.3 Return Expected Response

public class ListTest {
    @Test
    public void testGet() {
        int index = 1;
        Integer expected = 100;
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.doAnswer(invocation -> {
            Integer value = invocation.getArgument(0);
            return value * 100;
        }).when(mockList).get(index);
        Integer actual = mockList.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4.2.4 Simulate No Return Value

public class ListTest {
    @Test
    public void testClear() {
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.doNothing().when(mockList).clear();
        mockList.clear();
        Mockito.verify(mockList).clear();
    }
}

4.2.5 Call Real Methods

public class ListTest {
    @Test
    public void testGet() {
        int index = 0;
        Integer expected = 100;
        List<Integer> oldList = new ArrayList<>();
        oldList.add(expected);
        List<Integer> spylist = PowerMockito.spy(oldList);
        PowerMockito.doCallRealMethod().when(spylist).get(index);
        Integer actual = spylist.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

4.3 The Main Differences Between the Two Modes

Both modes are used to mock object methods, and there is almost no difference when used under mock instances. However, when used with spy instances, the when().thenReturn() executes the original method, but the doReturn().when() does not.

Service Testing:

@Slf4j

@Service
public class UserService {
    public long getUserCount() {
        log.info("Call the methods of obtaining the number of users");
        return 0L;
    }
}

Testing under the when().thenReturn() mode:

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Test
    public void testGetUserCount() {
        Long expected = 1000L;
        UserService userService = PowerMockito.spy(new UserService());
        PowerMockito.when(userService.getUserCount()).thenReturn(expected);
        Long actual = userService.getUserCount();
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

During the test, the "call the methods of obtaining the number of users" log will be printed.

Testing under the doReturn().when() mode:

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Test
    public void testGetUserCount() {
        Long expected = 1000L;
        UserService userService = PowerMockito.spy(new UserService());
        PowerMockito.doReturn(expected).when(userService).getUserCount();
        Long actual = userService.getUserCount();
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

During the test, the "call the methods of obtaining the number of users" log will not be printed.

4.4 WhenNew Simulation for Construction Method

Statement:

PowerMockito.whenNew(MockClass.class).withNoArguments().thenReturn(expectedObject) ;

PowerMockito.whenNew(MockClass.class).withArguments(someArgs).thenReturn(expectedObject) .

Purpose:

It is used to simulate the construction method.

Case:

public final class FileUtils {
    public static boolean isFile(String fileName) {
        return new File(fileName).isFile();
    }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest({FileUtils.class})
public class FileUtilsTest {
    @Test
    public void testIsFile() throws Exception {
        String fileName = "test.txt";
        File file = PowerMockito.mock(File.class);
        PowerMockito.whenNew(File.class).withArguments(fileName).thenReturn(file);
        PowerMockito.when(file.isFile()).thenReturn(true);
        Assert.assertTrue("Return values are false", FileUtils.isFile(fileName));
    }
}

Note: The @PrepareForTest({FileUtils.class}) is required. Otherwise, the simulation method does not take effect.

5. Argument Matcher

When executing unit testing that occasionally does not care about the values of the incoming parameters, users can use the argument matcher.

5.1 Argument Matcher (Any)

Mockito provides Mockito.anyInt(), Mockito.anyString, and Mockito.any(Class clazz) to indicate arbitrary values.

public class ListTest {
    @Test
    public void testGet() {
        int index = 1;
        Integer expected = 100;
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.when(mockList.get(Mockito.anyInt())).thenReturn(expected);
        Integer actual = mockList.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

5.2 Argument Matcher (Eq)

When using the argument matcher, all parameters should be used in the matcher. If users want to specify a specific value for a parameter, the Mockito.eq() can be applied.

@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {
    @Test
    public void testStartWith() {
        String string = "abc";
        String prefix = "b";
        boolean expected = true;
        PowerMockito.spy(StringUtils.class);
        PowerMockito.when(StringUtils.startsWith(Mockito.anyString(), Mockito.eq(prefix))).thenReturn(expected);
        boolean actual = StringUtils.startsWith(string, prefix);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

5.3 Additional Matcher

The AdditionalMatchers class of Mockito provides some rarely used argument matchers. Users can perform comparison operations, such as parameters to be greater than (gt), less than (lt), greater than or equal to (geq), and less than or equal to (leq). Logical calculations, including and or and not can be performed as well.

public class ListTest {
    @Test
    public void testGet() {
        int index = 1;
        Integer expected = 100;
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.when(mockList.get(AdditionalMatchers.geq(0))).thenReturn(expected);
        PowerMockito.when(mockList.get(AdditionalMatchers.lt(0))).thenThrow(new IndexOutOfBoundsException());
        Integer actual = mockList.get(index);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

6. Verify Statements

Verification confirms whether the method under the test has interacted with any of its dependent methods expectedly during the simulation.

Format:

Mockito.verify(mockObject[,times(int)]).someMethod(somgArgs)

Purpose:

It can be used to mock objects methods. Return the expected value, exception, response, or call real methods without executing the original method

Case:

6.1 Verify Call Methods

public class ListTest {
    @Test
    public void testGet() {
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.doNothing().when(mockList).clear();
        mockList.clear();
        Mockito.verify(mockList).clear();
    }
}

6.2 Verify the Number of Calls

public class ListTest {
    @Test
    public void testGet() {
        List<Integer> mockList = PowerMockito.mock(List.class);
        PowerMockito.doNothing().when(mockList).clear();
        mockList.clear();
        Mockito.verify(mockList, Mockito.times(1)).clear();
    }
}

In addition to times, Mockito supports verifiers, such as atLeastOnce, atLeast, only, atMostOnce, and atMost.

6.3 Verify Call Sequence

public class ListTest {
    @Test
    public void testAdd() {
           List<Integer> mockedList = PowerMockito.mock(List.class);
        PowerMockito.doReturn(true).when(mockedList).add(Mockito.anyInt());
        mockedList.add(1);
        mockedList.add(2);
        mockedList.add(3);
        InOrder inOrder = Mockito.inOrder(mockedList);
        inOrder.verify(mockedList).add(1);
        inOrder.verify(mockedList).add(2);
        inOrder.verify(mockedList).add(3);
    }
}

6.4 Verify Call Parameters

public class ListTest {
    @Test
    public void testArgumentCaptor() {
        Integer[] expecteds = new Integer[] {1, 2, 3};
        List<Integer> mockedList = PowerMockito.mock(List.class);
        PowerMockito.doReturn(true).when(mockedList).add(Mockito.anyInt());
        for (Integer expected : expecteds) {
            mockedList.add(expected);
        }
        ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class);
        Mockito.verify(mockedList, Mockito.times(3)).add(argumentCaptor.capture());
        Integer[] actuals = argumentCaptor.getAllValues().toArray(new Integer[0]);
        Assert.assertArrayEquals("Return values are not equal", expecteds, actuals);
    }
}

6.5 Ensure Verification Is Complete

Mockito supports the Mockito.verifyNoMoreInteractions, which can be used after all verification methods have been completed to ensure that all calls are verified. If there are any unverified calls to the mock objects, the nointeractionsprotected exception will occur.

public class ListTest {
    @Test
    public void testVerifyNoMoreInteractions() {
        List<Integer> mockedList = PowerMockito.mock(List.class);
        Mockito.verifyNoMoreInteractions(mockedList); // Normal execution
        mockedList.isEmpty();
        Mockito.verifyNoMoreInteractions(mockedList); // Exception thrown
    }
}

Note: The Mockito.verifyZeroInteractions is the same as the Mockito.verifyNoMoreInteractions, but the former has been phased out.

6.6 Verify Static Method

Mockito has no verification method for static methods, but PowerMock can provide support in this situation.

@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {
    @Test
    public void testVerifyStatic() {
        PowerMockito.mockStatic(StringUtils.class);
        String expected = "abc";
        StringUtils.isEmpty(expected);
        PowerMockito.verifyStatic(StringUtils.class);
        ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
        StringUtils.isEmpty(argumentCaptor.capture());
        Assert.assertEquals("Parameters are not equal", argumentCaptor.getValue(), expected);
    }
}

7. Private Properties

7.1 ReflectionTestUtils.setField

When using native JUnit for unit testing, the ReflectionTestUtils.setField is generally performed to set the private property value.

@Service
public class UserService {
    @Value("${system.userLimit}")
    private Long userLimit;
    public Long getUserLimit() {
        return userLimit;
    }
}

public class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    public void testGetUserLimit() {
        Long expected = 1000L;
        ReflectionTestUtils.setField(userService, "userLimit", expected);
        Long actual = userService.getUserLimit();
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

Note: In the test class, the UserService instance is loaded using the @Autowired. If this instance is referenced by a dynamic proxy, the ReflectionTestUtils.setField sets the proxy instance, resulting in the settings not taking effect.

7.2 Whitebox.setInternalState

When using PowerMock for unit tests, Whitebox.setInternalState can be performed to set the private property value.

@Service
public class UserService {
    @Value("${system.userLimit}")
    private Long userLimit;
    public Long getUserLimit() {
        return userLimit;
    }
}

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @InjectMocks
    private UserService userService;
    @Test
    public void testGetUserLimit() {
        Long expected = 1000L;
        Whitebox.setInternalState(userService, "userLimit", expected);
        Long actual = userService.getUserLimit();
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

Note: @RunWith (powermoccrunner. class) should be added.

8. Private Methods

8.1 Simulate Private Methods

8.1.1 Implement through the When Statements

public class UserService {
    private Long superUserId;
    public boolean isNotSuperUser(Long userId) {
        return !isSuperUser(userId);
    }
    private boolean isSuperUser(Long userId) {
        return Objects.equals(userId, superUserId);
    }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest {
    @Test
    public void testIsNotSuperUser() throws Exception {
        Long userId = 1L;
        boolean expected = false;
        UserService userService = PowerMockito.spy(new UserService());
        PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected);
        boolean actual = userService.isNotSuperUser(userId);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

8.1.2 Implement through Stub

Private methods can also be implemented by simulating a method stub. However, only the return value of the entire method can be simulated, not the return value of the specified parameter.

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest {
    @Test
    public void testIsNotSuperUser() throws Exception {
        Long userId = 1L;
        boolean expected = false;
        UserService userService = PowerMockito.spy(new UserService());
        PowerMockito.stub(PowerMockito.method(UserService.class, "isSuperUser", Long.class)).toReturn(!expected);
        boolean actual = userService.isNotSuperUser(userId);
        Assert.assertEquals("Return values are not equal", expected, actual;
    }
}

8.3 Test Private Methods

@RunWith(PowerMockRunner.class)
public class UserServiceTest9 {
    @Test
    public void testIsSuperUser() throws Exception {
        Long userId = 1L;
        boolean expected = false;
        UserService userService = new UserService();
        Method method = PowerMockito.method(UserService.class, "isSuperUser", Long.class);
        Object actual = method.invoke(userService, userId);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

8.4 Verify Private Methods

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest10 {
    @Test
    public void testIsNotSuperUser() throws Exception {
        Long userId = 1L;
        boolean expected = false;
        UserService userService = PowerMockito.spy(new UserService());
        PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected);
        boolean actual = userService.isNotSuperUser(userId);
        PowerMockito.verifyPrivate(userService).invoke("isSuperUser", userId);
        Assert.assertEquals("Return values are not equal", expected, actual);
    }
}

Here, the method can also be used for simulation and verification.

9. Main Annotations

PowerMock provides a series of annotations to better support the SpringMVC/SpringBoot project, which greatly simplifies the test code.

9.1 @RunWith

@RunWith(PowerMockRunner.class)

Specify that the JUnit uses the unit testing runner in the PowerMock framework

9.2 @PrepareForTest

@PrepareForTest({ TargetClass.class })

Add the @PrepareForTest and specify the class where the method is located to simulate a final class, final method, or static method. Add classes in {} and separate them with commas to specify multiple classes.

9.3 @Mock

The @Mock creates an instance of all mock, and all properties and methods are left blank with 0 or null.

9.4 @Spy

The @Spy creates an instance without mock. All member methods will be executed according to the logic of the original method until a specific value is returned by mock.

Note: Variables with @Spy must be initialized, otherwise, an exception will occur during the execution.

9.5 @InjectMocks

The @InjectMocks creates an instance that can call the code. Other instances created with the @Mock or @Spy will be applied to this instance.

@Service
public class UserService {
    @Autowired
    private UserDAO userDAO;
    public void modifyUser(UserVO userVO) {
        UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userVO, userDO);
        userDAO.modify(userDO);
    }
}

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Mock
    private UserDAO userDAO;
    @InjectMocks
    private UserService userService;
    @Test
    public void testCreateUser() {
        UserVO userVO = new UserVO();
        userVO.setId(1L);
        userVO.setName("changyi");
        userVO.setDesc("test user");
        userService.modifyUser(userVO);
        ArgumentCaptor<UserDO> argumentCaptor = ArgumentCaptor.forClass(UserDO.class);
        Mockito.verify(userDAO).modify(argumentCaptor.capture());
        UserDO userDO = argumentCaptor.getValue();
        Assert.assertNotNull("User instance is null", userDO);
        Assert.assertEquals("User identifications are not equal", userVO.getId(), userDO.getId());
        Assert.assertEquals("User names are not equal", userVO.getName(), userDO.getName());
        Assert.assertEquals("User descriptions are not equal", userVO.getDesc(), userDO.getDesc());
    }
}

9.6 @Captor

The @Captor creates an ArgumentCaptor at the field level. However, before starting the test method, MockitoAnnotations.openMocks(this) should be called for initialization.

@Service
public class UserService {
    @Autowired
    private UserDAO userDAO;
    public void modifyUser(UserVO userVO) {
        UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userVO, userDO);
        userDAO.modify(userDO);
    }
}

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Mock
    private UserDAO userDAO;
    @InjectMocks
    private UserService userService;
    @Captor
    private ArgumentCaptor<UserDO> argumentCaptor;
    @Before
    public void beforeTest() {
        MockitoAnnotations.openMocks(this);
    }
    @Test
    public void testCreateUser() {
        UserVO userVO = new UserVO();
        userVO.setId(1L);
        userVO.setName("changyi");
        userVO.setDesc("test user");
        userService.modifyUser(userVO);
        Mockito.verify(userDAO).modify(argumentCaptor.capture());
        UserDO userDO = argumentCaptor.getValue();
        Assert.assertNotNull("User instance is null", userDO);
        Assert.assertEquals("User identifications are not equal", userVO.getId(), userDO.getId());
        Assert.assertEquals("User names are not equal", userVO.getName(), userDO.getName());
        Assert.assertEquals("User descriptions are not equal", userVO.getDesc(), userDO.getDesc());
    }
}

9.7 @PowerMockIgnore

@PowerMockIgnore solves the ClassLoader error after using PowerMock

10. Relevant Views

10.1 Java Development Manual Specifications

Good unit testing must comply with the AIR principle shown below. Although the AIR principle appear simple, it is very critical to the testing quality. Good unit testing is generally automatic, independent, and repeatable.

A: Automatic
I: Independent
R: Repeatable

Unit testing must also be automatically executed in non-interactive ways. Test cases are usually executed regularly, and the execution process must be fully automated to make sense. A test output that requires manual checks is not good unit testing. System.out is not allowed to be used for manual verification in unit testing. You can use assert instead.

Unit testing can be repeated and cannot be affected by the external environment.

Note: Unit testing is usually put into continuous integration, and unit testing is executed each time when the code is checked in. If unit testing has dependencies on the external environment, such as a network, service, or middleware, the continuous integration mechanism may become unavailable.

Positive Example: In order not to be affected by the external environment, the design of the code must change the SUT dependency to injection and use a dependency injection (DI) framework, such as spring, to inject a native memory or mock during testing.

Recommendation: The unit test code is written to comply with the BCDE principle shown below to ensure the delivery quality of the tested modules.

B is for border, which means boundary value testing, including the cyclic border, special values, special time, and data sequence.

C is for correct, which means the correct input that gives the expected results.

D is for design, which means that unit tests are written in combination with the design documents.

E is for error, which means with the mandatory input of error information, such as illegal data, abnormal processes, and unauthorized services, to get the expected results.

10.2 Why Do We Use Mock?

According to the relevant information on the Internet, the opinions are summarized below:

  • Mock can remove external service dependencies to ensure the independence of test cases.
  • Today's Internet software systems usually use microservices deployed in a distributed manner. Users must prepare other services to unit test a service. This poses a great deal of tolerance and infeasibility.
  • Mock can reduce the preparation of comprehensive process test data, thus improving the speed of writing test cases.
  • Traditional integration tests require preparing test data for a comprehensive process. Some processes may not be familiar to users. Ultimately, the expected results may not be achieved even after spending a lot of time and effort. The current unit testing only needs to simulate the upstream input data and verify the output data to the downstream. The speed of writing test cases and testing can increase many times.
  • Mock can simulate some abnormal processes to ensure the code coverage of test cases.
  • According to the BCDE principle of unit tests, boundary value testing and mandatory input of error information are required to help cover the entire code logic. In real-world systems, it is difficult to construct these boundary values or trigger the error information. However, mock fundamentally solves this problem. No matter what boundary value or error information users want, you can use Mock.
  • Mock can avoid loading project environment configuration, thus ensuring the execution speed of test cases.

During integration testing, all environment configurations of the project should be added, and all service interfaces that the project depends on should be started as well. It often takes minutes (or even tens of minutes) to execute a test case. However, test cases implemented with Mock do not need to load project environment configurations or start other service interfaces. So the execution speed is often within a few seconds, greatly improving the execution speed of unit testing.

10.3 The Differences Between Unit Testing and Integration Testing

In practice, many people use integration testing instead of unit testing or confuse integration testing with unit testing. The differences between unit testing and integration testing are summarized below:

  • Different Test Objects

The object of unit testing is for the program unit to realize a specific function while the object of integration testing is the module in the outline design planning and the combination between modules.

  • Different Test Methods

The main method in unit testing is code-based white box testing, but the main method in integration testing is function-based black box testing.

  • Different Test Time

Integration testing is later than unit testing.

  • Different Test Content

Unit testing mainly tests logic, functions, parameter transfer, variable reference, error handling, requirements, and specific requirements in the design of programs in the modules. Integration testing mainly verifies each interface, the data transfer relationship between interfaces, and whether the combined modules can achieve the expected results.

0 1 0
Share on

Changyi

5 posts | 1 followers

You may also like

Comments

Changyi

5 posts | 1 followers

Related Products