Mastering Unit Testing with TestNG and PowerMock in Spring Boot

This article explains unit testing concepts, introduces TestNG and PowerMock frameworks for Spring Boot, details common annotations, provides Maven dependency snippets and comprehensive Java test examples, and demonstrates advanced mocking techniques, parameterized tests, and assertion strategies to improve code reliability and coverage.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Mastering Unit Testing with TestNG and PowerMock in Spring Boot

Unit testing (Unit Testing) refers to verifying the correctness of the smallest testable units in software, such as a module, function, or class, performed in isolation from other modules.

After development, we cannot guarantee 100% correctness. By writing unit tests, we define input‑output behavior, use assertions to check each case, and improve correctness, stability, reliability, and development speed. Common frameworks include Spring-Boot-Test, TestNG, and PowerMock. TestNG (Testing, Next Generation) is a next‑generation testing framework built on JUnit and NUnit ideas, supporting both unit and integration testing via annotations. PowerMock extends other mocking frameworks, providing custom class loaders and bytecode manipulation to mock static, constructor, private, and final methods.

Common Annotations

1. TestNG Annotations

@BeforeSuite

runs once before all tests in the suite. @AfterSuite runs once after all tests in the suite. @BeforeClass runs once before the first test method in the class. @AfterClass runs once after the last test method in the class. @BeforeMethod runs before each test method. @AfterMethod runs after each test method. @BeforeTest runs before all test methods inside a <test> tag. @AfterTest runs after all test methods inside a <test> tag. @DataProvider supplies a two‑dimensional Object[][] data source for @Test methods. @Parameters passes parameters from XML configuration to @Test methods. @Test marks a class or method as a test case.

2. PowerMock Annotations

@Mock

is a shortcut for Mockito.mock(). @InjectMocks injects existing mock objects into a bean by name. @Spy wraps a real object to allow behavior verification.

Example Code

1. Add pom.xml Dependencies

<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 Tests

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() {
        UserDto userDto = new UserDto();
        userDto.setId(100);
        when(userService.get()).thenReturn(userDto);
        OrderDto order = orderService.createOrder(new OrderDto());
        assertEquals(order.getId(), 100);
    }
    // Exception test
    @Test
    public void testCreateOrderEx() {
        when(userService.get()).thenThrow(new RuntimeException());
        Exception exception = null;
        try {
            orderService.createOrder(new OrderDto());
        } catch (RuntimeException e) {
            exception = e;
        }
        assertNotNull(exception);
    }
}

Common Mock Methods

1. Mock Static Methods

// 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 Fields

// field injection
ReflectionTestUtils.setField(orderService, "rateLimit", 99);

3. Mock Private Methods

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

Advanced Usage

1. Parameterized Batch Testing

When test data is large, use @DataProvider to generate a data source and reference it with @Test(dataProvider = "xxx"):

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;
    @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}};
    }
    @Test
    public void testCheckEffectiveUser() {
        UserDto dto = new UserDto();
        dto.setSex(1);
        dto.setFlag(1);
        dto.setAge(18);
        boolean result = userService.checkEffectiveUser(dto);
        assertTrue(result);
    }
    @Test(dataProvider = "test")
    public void testCheckEffectiveUser(UserDto dto, Object object) {
        boolean result = userService.checkEffectiveUser(dto);
        assertFalse(result);
    }
}

2. Complex Condition to Ensure Coverage

Effective user: age > 18, sex = 1, flag = 1.

public boolean checkEffectiveUser(UserDto dto) {
    return Objects.equals(dto.getSex(), 1) &&
           Objects.equals(dto.getFlag(), 1) &&
           dto.getAge() != null && dto.getAge() >= 18;
}

Refactor to simple if‑else for full coverage:

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;
}

Corresponding unit tests covering each return path:

public class UserServiceTest extends BaseTest {
    @InjectMocks
    private UserService userService;
    @Test public void testCheckEffectiveUser_0() { assertFalse(userService.checkEffectiveUser(new UserDto())); }
    @Test public void testCheckEffectiveUser_1() { UserDto d=new UserDto(); d.setSex(1); assertFalse(userService.checkEffectiveUser(d)); }
    @Test public void testCheckEffectiveUser_2() { UserDto d=new UserDto(); d.setSex(1); d.setFlag(1); assertFalse(userService.checkEffectiveUser(d)); }
    @Test public void testCheckEffectiveUser_3() { UserDto d=new UserDto(); d.setSex(1); d.setFlag(1); d.setAge(1); assertFalse(userService.checkEffectiveUser(d)); }
    @Test public void testCheckEffectiveUser_4() { UserDto d=new UserDto(); d.setSex(1); d.setFlag(1); d.setAge(18); assertTrue(userService.checkEffectiveUser(d)); }
}

3. Assert Method Parameters

Java assert keyword for debugging.

int a = 0, b = 1;
assert a == 0 && b == 0;
// compile with -ea to enable assertions

Spring Assert for validating incoming parameters.

public boolean checkUserAge(UserDto dto){
    Assert.notNull(dto.getAge(), "User age cannot be null");
    Assert.isTrue(dto.getAge() >= 18, "User age must be at least 18");
    return true;
}

Unified REST API error handling.

@ControllerAdvice
public class GlobalExceptionHandler {
    @ResponseBody
    @ExceptionHandler(IllegalArgumentException.class)
    public Response<String> handleArgError(IllegalArgumentException e){
        return new Response().failure().message(e.getMessage());
    }
}

Summary

When designing functional modules, follow principles such as moderate module size, appropriate call depth, high cohesion and low coupling, single entry/exit, limited data redundancy, and layered system decomposition, as recommended by software engineering design guidelines.

References

https://testng.org/doc/

https://github.com/powermock/powermock

https://www.netconcepts.cn/detail-41004.html

Unit testing coverage diagram
Unit testing coverage diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaunit testingMockingSpring BootPowerMockTestNG
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.