Unit testing – TestNG + PowerMock

Unit Testing refers to the testing work of checking the correctness of the smallest testable unit in the software or project. A unit is an artificially specified minimum testable functional module, which can be a module, a function or a class. Unit testing needs to be done in isolation from module development.

After the program development is completed, we often cannot guarantee that the program is 100% correct. By writing unit tests, we can define our input and output programs through automated test programs, check the results of each Case through assertions, and detect our program. To improve the correctness, stability, and reliability of the program and save program development time. The main unit testing frameworks we use in the project include Spring-Boot-Test TestNG, PowerMock, etc.

TestNG, that is, Testing, Next Generation, the next generation testing technology, is a testing framework built based on the ideas of JUnit and NUnit that uses annotations to strengthen testing functions, namely It can be used for unit testing or integration testing.

PowerMock is also a unit test simulation framework, which is an extension based on other unit test simulation frameworks. By providing a customized class loader and the application of some bytecode tampering techniques, PowerMock now supports simulation of static methods, constructors, private methods and Final methods, and removes the static initialization process. and other powerful functions.

Commonly used annotations

1. TestNG annotations

  • @BeforeSuite Runs only once before all tests of the suite have run on the annotated method
  • @AftereSuite Runs only once after all tests of the suite have been run on the annotated method
  • @BeforeClass runs before calling the first test method of the current class. Annotated methods only run once.
  • @AftereClass runs after calling the first test method of the current class. Annotated methods only run once.
  • @BeforeMethod annotated method will be run before each test method
  • @AfterMethod annotated method will be run after each test method
  • @BeforeTest The annotated method will be run before all test methods belonging to the class within the test tag are run.
  • @AfterTest The annotated method will be run after all test methods belonging to the class within the test tag are run.
  • @DataProvider Marks a method to provide data for the test method. The annotation method must return an Object [] [], where each Object [] can be assigned to the parameter list of the test method. @Test methods that want to receive data from this DataProvider need to use a dataProvider name equal to this annotation name
  • @Parameters describes how to pass parameters to the @Test method; suitable for parameterized value passing in xml mode
  • @Test marks a class or method as part of the test. If this mark is placed on a class, all public methods of the class will be used as test methods.

2. PowerMock annotations

  • The @Mock annotation is actually the abbreviation of the Mockito.mock() method, we only use it in the test class;
  • @InjectMocks actively injects existing mock objects into beans by name, but no exception will be thrown if the injection fails;
  • @Spy encapsulates a real object so that the object’s behavior can be tracked and set like other mock objects;
Now I have also found a lot of test friends and created a communication group to share technology, sharing a lot of technical documents and video tutorials we collected.
If you don’t want to experience the feeling of not being able to find resources when studying on your own, having no one to answer your questions, and persisting for a few days before giving up.
You can join us to communicate. And there are many technical experts who have made certain achievements in automation, performance, security, test development, etc.
Share their experience, and also share many live lectures and technical salons
You can learn for free! Focus on it! Open source! ! !
QQ group number: 110685036

Example code

1. Add pom.xml dependency

Taking the Spring-Boot project as an example, first we need to add TestNG + ProwerMock dependencies as follows:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>${testng.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-testng</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>

2. Add unit test

Add test code

import com.test.testng.dto.OrderDto;
import com.test.testng.dto.UserDto;
import org.mockito.*;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
public class OrderServiceTest extends PowerMockTestCase {
    @BeforeMethod
    public void before() {
        MockitoAnnotations.openMocks(this);
    }
    @InjectMocks
    private OrderService orderService;
    @Mock
    private UserService userService;
    // normal test
    @Test
    public void testCreateOrder() {
        //1. mock method start
        UserDto userDto = new UserDto();
        userDto.setId(100);
        when(userService.get()).thenReturn(userDto);
        //2. call business method
        OrderDto order = orderService.createOrder(new OrderDto());
        //3.assert
        assertEquals(order.getId(), 100);
    }
    //Exception test
    @Test
    public void testCreateOrderEx() {
        //1. mock method start
        when(userService.get()).thenThrow(new RuntimeException());
        Exception exception = null;
        try {
            //2. call business method
            orderService.createOrder(new OrderDto());
        } catch (RuntimeException e) {
            exception = e;
        }
        //3.assert
        assertNotNull(exception);
    }
}

Common Mock methods

1. Mock static method

//Static method
UserDto dto = new UserDto();
dto.setId(100000);
PowerMockito.mockStatic(UserService.class);
PowerMockito.when(UserService.loginStatic()).thenReturn(dto);
UserDto userDto = UserService.loginStatic();
assertEquals(100000, userDto.getId().intValue());

2. Mock private properties

//Field assignment
ReflectionTestUtils.setField(orderService, "rateLimit", 99);

3. Mock private method

//Mock private methods
MemberModifier.stub(MemberMatcher.method(UserService.class, "get1")).toReturn(new UserDto());
//Test private method
Method method = PowerMockito.method(UserService.class, "get1", Integer.class);
Object userDto = method.invoke(userService, 1);
assertTrue(userDto instanceof UserDto);

Advanced use

1. Parameterized batch testing

When there is a lot of test data, we can generate a data source through @DataProvider and use the data through @Test(dataProvider = "xxx"), as shown below:

import com.test.testng.BaseTest;
import com.test.testng.dto.UserDto;
import org.mockito.InjectMocks;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
public class UserServiceTest2 extends BaseTest {
    @InjectMocks
    private UserService userService;
    //Define data source
    @DataProvider(name = "test")
    public static Object[][] userList() {
        UserDto dto1 = new UserDto();
        UserDto dto2 = new UserDto();
        dto2.setSex(1);
        UserDto dto3 = new UserDto();
        dto3.setSex(1);
        dto3.setFlag(1);
        UserDto dto4 = new UserDto();
        dto4.setSex(1);
        dto4.setFlag(1);
        dto4.setAge(1);
        return new Object[][] {<!-- -->{dto1, null}, {dto2, null}, {dto3, null}, {dto4, null}};
    }
    // Correct scenario
    @Test
    public void testCheckEffectiveUser() {
        UserDto dto = new UserDto();
        dto.setSex(1);
        dto.setFlag(1);
        dto.setAge(18);
        boolean result = userService.checkEffectiveUser(dto);
        assertTrue(result);
    }

    // error scenario
    @Test(dataProvider = "test")
    public void testCheckEffectiveUser(UserDto dto, Object object) {
        boolean result = userService.checkEffectiveUser(dto);
        assertFalse(result);
    }

}

Case:

  1. Determine valid users: age is greater than 18 and sex = 1 and flag = 1
public boolean checkEffectiveUser(UserDto dto) {
    // Determine valid users: age is greater than 18 and sex = 1 and flag = 1
    return Objects.equals(dto.getSex(), 1) & amp; & amp;
        Objects.equals(dto.getFlag(), 1) & amp; & amp;
        dto.getAge() != null & amp; & amp; dto.getAge() >= 18;
}
  1. Split logic. Convert this into the simplest if … else statement. Then add unit tests as follows:
public boolean checkEffectiveUser(UserDto dto) {
    if (!Objects.equals(dto.getSex(), 1)) {
        return false;
    }
    if (!Objects.equals(dto.getFlag(), 1)) {
        return false;
    }
    if (dto.getAge() == null) {
        return false;
    }
    if (dto.getAge() < 18) {
        return false;
    }
    return true;
}
  1. After splitting, we can see that we only need 5 unit tests to achieve full coverage.
public class UserServiceTest extends BaseTest {
    @InjectMocks
    private UserService userService;
    // Overwrite the first return
    @Test
    public void testCheckEffectiveUser_0() {
        UserDto dto =new UserDto();
        boolean result = userService.checkEffectiveUser(dto);
        assertFalse(result);
    }
    // Overwrite the second return
    @Test
    public void testCheckEffectiveUser_1() {
        UserDto dto =new UserDto();
        dto.setSex(1);
        boolean result = userService.checkEffectiveUser(dto);
        assertFalse(result);
    }
    // Overwrite the third return
    @Test
    public void testCheckEffectiveUser_2() {
        UserDto dto =new UserDto();
        dto.setSex(1);
        dto.setFlag(1);
        boolean result = userService.checkEffectiveUser(dto);
        assertFalse(result);
    }
    // Overwrite the fourth return
    @Test
    public void testCheckEffectiveUser_3() {
        UserDto dto =new UserDto();
        dto.setSex(1);
        dto.setFlag(1);
        dto.setAge(1);
        boolean result = userService.checkEffectiveUser(dto);
        assertFalse(result);
    }
    // Overwrite the fifth return
    @Test
    public void testCheckEffectiveUser_4() {
        UserDto dto =new UserDto();
        dto.setSex(1);
        dto.setFlag(1);
        dto.setAge(18);
        boolean result = userService.checkEffectiveUser(dto);
        assertTrue(result);
    }
}
  1. Single test coverage detection detection

3. Verify method parameters through assertions

  1. assert: Assertion is a reserved word of Java, used to debug the program, followed by a logical operation expression, as follows:
int a = 0, b = 1;
assert a == 0 & amp; & amp; b == 0;
// How to use: javac compiles the source file, and then java -ea class file name. 
  1. In Spring-Boot, you can use the Assert class method provided by Spring to verify the parameters from the front end, such as:
// Check age >= 18 years old
public boolean checkUserAge(UserDto dto){
    Assert.notNull(dto.getAge(), "User age cannot be null");
    Assert.isTrue(dto.getAge() >= 18, "The user's age cannot be less than 18 years old");
    return Boolean.TRUE;
}
  1. If it needs to be converted into a unified corresponding message returned by rest api, we can pass:
@ControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ExceptionHandler(value = IllegalArgumentException.class)
    public Response<String> handleArgError(IllegalArgumentException e){
        return new Response().failure().message(e.getMessage());
    }
}

Summary

In principle, we should follow the following principles during the design process of functional modules (refer to “Software Engineering-Structured Design Guidelines”):

  1. Moderately sized module
  2. Proper system call depth
  3. More fan-in, less fan-out (increase reuse, reduce dependence)
  4. Single entrance, single exit
  5. The scope of the module should be within the module
  6. Functionality should be predictable
  7. High cohesion, low coupling
  8. System decomposition is hierarchical
  9. Less data redundancy

The following are supporting learning materials, which should be the most comprehensive and complete preparation warehouse for friends who do [software testing]. This warehouse has also accompanied me through the most difficult journey. I hope it can also help you!

Software Testing Interview Mini Program

A software test question bank that has been used by millions of people! ! ! Who is who knows! ! ! The most comprehensive interview test mini program on the Internet, you can use your mobile phone to answer questions, take the subway, bus, and roll it up!

Covering these interview question sections:

1. Basic theory of software testing, 2. web, app, interface function testing, 3. network, 4. database, 5. linux

6. Web, app, interface automation, 7. Performance testing, 8. Programming basics, 9. HR interview questions, 10. Open test questions, 11. Security testing, 12. Computer basics

How to obtain data: