PowerMock Java unit testing techniques

Preface

Gaode’s technical tycoon Xiang Teacher said when talking about methodology: "Complex problems should be simplified, and simple problems should be deepened."
This sentence made me feel deeply. Isn't this a set of methods for writing code— —Split a complex logic into many simple logics, and then implement each simple logic in depth, and finally integrate these simple logics into complex logics, summed up as the mantra of eight characters, which means "transform the complex into simple, from simple to complex".
Writing Java unit test cases is actually to "simplify complex problems"-that is, to disassemble a complex code into a series of simple unit test cases; writing Java unit test cases is actually to "simplify simple problems" In-depth"-that is, learn a set of methods, summarize a set of patterns and apply them to practice. Here, the author summarizes some Java unit testing techniques based on daily work experience for everyone to communicate and learn.

1. Prepare the environment

PowerMock is a more powerful framework that extends other mock frameworks such as EasyMock. PowerMock uses a custom class loader and bytecode operations to simulate static methods, construction methods, final classes and methods, private methods, remove static initializers, and so on.

1.1. Introduce the PowerMock package

In order to import the PowerMock package, you need to add the following maven dependencies in the pom.xml file:

1.2. Integrate SpringMVC project

In the SpringMVC project, you need to add JUnit's maven dependency in the pom.xml file:

1.3. Integrate the SpringBoot project

In the SpringBoot project, you need to add JUnit's maven dependency in the pom.xml file:

1.4. A simple test case

Here, take List as an example to simulate a non-existent list, but 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("返回值不相等", expected, actual);
    }
}

2. mock statement

2.1. mock method

statement:

T PowerMockito.mock(Class clazz);

use:

Can be used to simulate object instances of the specified class.
When simulating non-final methods of non-final classes (interfaces, ordinary classes, virtual base classes), it is not necessary to use @RunWith and @PrepareForTest annotations. When simulating a final class or final method, you must use @RunWith and @PrepareForTest annotations. The annotation form is like:
@RunWith(PowerMockRunner.class)
@PrepareForTest({TargetClass.class})

2.1.1. Simulate common methods of non-final classes

@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("返回值不相等", 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("返回值不相等", expectArea, actualArea, 1E-6D);
    }
}

2.2. mockStatic method

Declaration:
PowerMockito.mockStatic(Class clazz);
Purpose: It
can be used to simulate static methods of a class, and must be annotated with "@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("返回值不相等", expected, actual);
    }
}

3. spy statement

If an object, we only want to simulate some of its methods, and hope that other methods are the same as before, you can use the PowerMockito.spy method instead of the PowerMockito.mock method. Therefore, the method set by the when statement calls the simulation method; the method not set by the when statement calls the original method.

3.1. spy class

Declaration:
PowerMockito.spy(Class clazz);
Purpose:
Used to simulate some methods of the 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("返回值不相等", expected, actual);
    }
}

3.2. spy object

Declaration:
T PowerMockito.spy(T object);
Purpose:
Used to simulate some methods of the object.
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("返回值不相等", expected, actual);
    }
}

4. The when statement

4.1. when().thenReturn() 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:
Used to simulate object methods, execute the original method first, and then return the expected value, exception, response, or call the real method.

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("返回值不相等", 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("返回值不相等", 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("返回值不相等", expected, actual);
    }
}

4.1.4. Call the real method

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("返回值不相等", expected, actual);
    }
}

4.2. doReturn().when() mode

Declaration:
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:
used to simulate object methods, directly return expected values, exceptions , Reply, or call the real method without executing the original method.
Note:
Never 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 there will be no compilation error, an UnfinishedStubbingException will be thrown 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("返回值不相等", 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("返回值不相等", 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("返回值不相等", expected, actual);
    }
}

4.2.4. Simulation without 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. Calling the real method

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("返回值不相等", expected, actual);
    }
}

4.3. The main difference between the two modes

Both modes are used to simulate object methods, and when used under mock instances, there is basically no difference. However, when used in a spy instance, when().thenReturn() mode will execute the original method, while doReturn().when() mode will not execute the original method.
Test service class:

@Slf4j

@Service
public class UserService {
    public long getUserCount() {
        log.info("调用获取用户数量方法");
        return 0L;
    }
}

Use when().thenReturn() pattern:

@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("返回值不相等", expected, actual);
    }
}

During the test, the log of "calling the method of obtaining the number of users" will be printed out.

Use 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("返回值不相等", expected, actual);
    }
}

During the test, the log of "calling the method of obtaining the number of users" will not be printed out.

4.4. whenNew simulation construction method

statement:

PowerMockito.whenNew(MockClass.class).withNoArguments().thenReturn(expectedObject);
PowerMockito.whenNew(MockClass.class).withArguments(someArgs).thenReturn(expectedObject);
Purpose:
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("返回值为假", FileUtils.isFile(fileName));
    }
}

Note: You need to add the annotation @PrepareForTest({FileUtils.class}), otherwise the simulation method will not take effect.

5. Parameter matcher

When performing unit tests, sometimes you do not care about the value of the passed in parameters, you can use a parameter matcher.

5.1. Parameter matcher (any)

Mockito provides Mockito.anyInt(), Mockito.anyString, Mockito.any(Class clazz), etc. to represent 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("返回值不相等", expected, actual);
    }
}

5.2. Parameter matcher (eq)

When we use parameter matchers, all parameters should use matchers. If you want to specify a specific value for a parameter, you need to use the Mockito.eq() method.

@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("返回值不相等", expected, actual);
    }
}

5.3. Additional matchers

The AdditionalMatchers class of Mockito provides some rarely used parameter matchers. We can perform comparison operations such as parameter greater than (gt), less than (lt), greater than or equal to (geq), less than or equal to (leq), and can also perform parameter and ( and), or (or), not (not) and other logical calculations.

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("返回值不相等", expected, actual);
    }
}

6. verify statement

Verification is to confirm whether the method under test has interacted with any of its dependent methods as expected during the simulation process.

format:

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

use:

Used to simulate the object method, directly return the expected value, exception, response, or call the real method without executing the original method.

Case:

6.1. Verify the calling method

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. Number of verification 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 also supports atLeastOnce, atLeast, only, atMostOnce, atMost and other times validators.

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("返回值不相等", expecteds, actuals);
    }
}

6.5. Ensure verification is complete

Mockito provides the Mockito.verifyNoMoreInteractions method, which can be used after all verification methods to ensure that all calls are verified. If there are any unverified calls on the mock object, a NoInteractionsWanted exception will be thrown.

public class ListTest {
    @Test
    public void testVerifyNoMoreInteractions() {
        List<Integer> mockedList = PowerMockito.mock(List.class);
        Mockito.verifyNoMoreInteractions(mockedList); // 执行正常
        mockedList.isEmpty();
        Mockito.verifyNoMoreInteractions(mockedList); // 抛出异常
    }
}

Note: The Mockito.verifyZeroInteractions method is the same as the Mockito.verifyNoMoreInteractions method, but has been deprecated.

6.6. Validating static methods

Mockito does not have a static verification method, but PowerMock provides support in this regard.

@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("参数不相等", argumentCaptor.getValue(), expected);
    }
}

7. Private Properties

7.1. ReflectionTestUtils.setField方法

When using native JUnit for unit testing, we generally use the ReflectionTestUtils.setField method to set private property values.

@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("返回值不相等", expected, actual);
    }
}

Note: In the test class, the UserService instance is loaded through the @Autowired annotation. If the instance has been dynamically proxied, the ReflectionTestUtils.setField method sets the proxy instance, which causes the setting to not take effect.

7.2. Whitebox.setInternalState方法

Now when using PowerMock for unit testing, you can use the Whitebox.setInternalState method to set private property values.

@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("返回值不相等", expected, actual);
    }
}

Note: Need to add annotation @RunWith(PowerMockRunner.class).

8. Private methods

8.1. Simulating private methods

8.1.1. Realized by when

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("返回值不相等", expected, actual);
    }
}

8.1.2. Implementation via stub

By simulating method stub (stub), it is also possible to simulate private methods. However, it can only simulate the return value of the entire method, 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("返回值不相等", expected, actual;
    }
}

8.3. Testing 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("返回值不相等", 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("返回值不相等", expected, actual);
    }
}

Here, you can also use the Method set of methods for simulation and verification methods.

9. Main notes

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

9.1. @RunWith annotation

@RunWith(PowerMockRunner.class)

Specify JUnit to use the unit test runner in the PowerMock framework.

9.2. @PrepareForTest annotation

@PrepareForTest({ TargetClass.class })

When you need to simulate a final class, final method, or static method, you need to add the @PrepareForTest annotation and specify the class in which the method is located. If you need to specify multiple classes, add multiple classes in {} and separate them with commas.

9.3. @Mock annotation

The @Mock annotation creates an instance of all Mock, and all properties and methods are blanked (0 or null).

9.4. @Spy annotation

The @Spy annotation creates an instance without Mock, and all member methods will be executed according to the logic of the original method until a specific value is returned by the Mock.

Note: @Spy annotated variables need to be initialized, otherwise an exception will be thrown during execution.

9.5. @InjectMocks annotation

The @InjectMocks annotation creates an instance, this instance can call the method of the real code, and the remaining instances created with the @Mock or @Spy annotation will be injected into the 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("用户实例为空", userDO);
        Assert.assertEquals("用户标识不相等", userVO.getId(), userDO.getId());
        Assert.assertEquals("用户名称不相等", userVO.getName(), userDO.getName());
        Assert.assertEquals("用户描述不相等", userVO.getDesc(), userDO.getDesc());
    }
}

9.6. @Captor annotation

The @Captor annotation creates a parameter capturer at the field level. However, before the test method starts, it must be initialized by calling MockitoAnnotations.openMocks(this).

@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("用户实例为空", userDO);
        Assert.assertEquals("用户标识不相等", userVO.getId(), userDO.getId());
        Assert.assertEquals("用户名称不相等", userVO.getName(), userDO.getName());
        Assert.assertEquals("用户描述不相等", userVO.getDesc(), userDO.getDesc());
    }
}

9.7. @PowerMockIgnore annotation

In order to solve the problem of ClassLoader error after using PowerMock.

10. Related Views

10.1. "Java Development Manual" specification

[Mandatory] Good unit tests must comply with AIR principles. Note: When the unit test is running online, it feels like air (AIR), but it is very important to guarantee the quality of the test. On the macro level, a good unit test has the characteristics of automation, independence, and repeatable execution.

A: Automatic

I: Independent

R: Repeatable (repeatable)

[Mandatory] Unit testing should be fully automated and non-interactive. Test cases are usually executed on a regular basis, and the execution process must be fully automated to make sense. A test whose output requires manual inspection is not a good unit test. It is not allowed to use System.out to verify human flesh in unit testing, and must use assert to verify.

[Mandatory] Unit testing can be executed repeatedly and cannot be affected by the external environment.

Note: Unit tests are usually put into continuous integration, and unit tests will be executed every time there is a code check in. If the single test is dependent on the external environment (network, service, middleware, etc.), it will easily lead to the unavailability of the continuous integration mechanism.

Positive example: In order not to be affected by the external environment, it is required to change the dependency of the SUT to injection when designing the code, and use a DI framework such as spring to inject a local (memory) implementation or a mock implementation during testing.

[Recommended] Write unit test code to comply with the BCDE principle to ensure the delivery quality of the tested module.

B: Border, boundary value test, including loop boundary, special value, special time point, data sequence, etc.

C: Correct, input the correct, and get the expected result.

D: Design, combined with design documents to write unit tests.

E: Error, compulsory input of wrong information (such as: illegal data, abnormal process, outside business permission, etc.), and get the expected result.

10.2. Why use Mock?

According to relevant information on the Internet, the conclusions are as follows:

Mock can be used to relieve external service dependencies, thereby ensuring the independence of test cases.

Today's Internet software systems usually use distributed deployment of microservices, and prepare other services for unit testing of a certain service, which is extremely dependent and infeasible.

Mock can reduce the preparation of full-link test data, thereby increasing the speed of writing test cases.

Traditional integration testing requires the preparation of full-link test data, and some links may not be familiar to you. In the end, it takes a lot of time and experience and does not necessarily get the results you want. The current unit test only needs to simulate the upstream input data and verify the downstream output data. The speed of writing test cases and testing can be increased many times.

Mock can simulate some abnormal processes, thereby ensuring the code coverage of test cases.

According to the BCDE principle of unit testing, boundary value testing (Border) and mandatory error message input (Error) are required, which helps to cover the entire code logic. In an actual system, it is difficult to construct these boundary values, and it can also be difficult to trigger these error messages. And Mock fundamentally solves this problem: What kind of boundary value you want, you only need to Mock; what kind of error message you want, you only need to Mock.

Mock does not need to load the project environment configuration, thus ensuring the execution speed of test cases.

During integration testing, we need to load all the environment configurations of the project and start all the service interfaces that the project depends on. It often takes several minutes or even tens of minutes to execute a test case. The test cases implemented by Mock do not need to load the project environment configuration or rely on other service interfaces. The execution speed is often within a few seconds, which greatly improves the execution speed of the unit test.

10.3. The difference between unit testing and integration testing

In actual work, many students use integration testing instead of unit testing, or think that integration testing is unit testing. Here, summarize the difference between unit testing and integration testing:

Different test objects The
unit test object is the program unit that realizes the specific function, and the integration test object is the module in the outline design plan and the combination of the modules.

Different testing methods
The main method in unit testing is code-based white-box testing, and function-based black-box testing is mainly used in integration testing.

The test time is different. The
integration test is later than the unit test.

The test content is different. The
unit test is mainly the logic, function, parameter transfer, variable reference, error handling and testing of the specific requirements in the design of the module; the integration test mainly verifies the data transfer relationship between each interface and the interface. And whether the combination of modules can achieve the expected results.

 

Original link

This article is the original content of Alibaba Cloud and may not be reproduced without permission.

Guess you like

Origin blog.csdn.net/weixin_43970890/article/details/114653612