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.
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.
PowerMockextends other mocking frameworks, providing custom class loaders and bytecode manipulation to mock static, constructor, private, and final methods.
Common Annotations
1. TestNG Annotations
@BeforeSuiteruns once before all tests in the suite.
@AfterSuiteruns once after all tests in the suite.
@BeforeClassruns once before the first test method in the class.
@AfterClassruns once after the last test method in the class.
@BeforeMethodruns before each test method.
@AfterMethodruns after each test method.
@BeforeTestruns before all test methods inside a
<test>tag.
@AfterTestruns after all test methods inside a
<test>tag.
@DataProvidersupplies a two‑dimensional
Object[][]data source for
@Testmethods.
@Parameterspasses parameters from XML configuration to
@Testmethods.
@Testmarks a class or method as a test case.
2. PowerMock Annotations
@Mockis a shortcut for
Mockito.mock().
@InjectMocksinjects existing mock objects into a bean by name.
@Spywraps a real object to allow behavior verification.
Example Code
1. Add pom.xml Dependencies
<code><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></code>2. Add Unit Tests
<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() {
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);
}
}
</code>Common Mock Methods
1. Mock Static Methods
<code>// 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());</code>2. Mock Private Fields
<code>// field injection
ReflectionTestUtils.setField(orderService, "rateLimit", 99);</code>3. Mock Private Methods
<code>// 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);
</code>Advanced Usage
1. Parameterized Batch Testing
When test data is large, use
@DataProviderto generate a data source and reference it with
@Test(dataProvider = "xxx"):
<code>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);
}
}
</code>2. Complex Condition to Ensure Coverage
Effective user: age > 18, sex = 1, flag = 1.
<code>public boolean checkEffectiveUser(UserDto dto) {
return Objects.equals(dto.getSex(), 1) &&
Objects.equals(dto.getFlag(), 1) &&
dto.getAge() != null && dto.getAge() >= 18;
}
</code>Refactor to simple if‑else for full coverage:
<code>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;
}
</code>Corresponding unit tests covering each return path:
<code>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)); }
}
</code>3. Assert Method Parameters
Java
assertkeyword for debugging.
<code>int a = 0, b = 1;
assert a == 0 && b == 0;
// compile with -ea to enable assertions
</code>Spring
Assertfor validating incoming parameters.
<code>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;
}
</code>Unified REST API error handling.
<code>@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(IllegalArgumentException.class)
public Response<String> handleArgError(IllegalArgumentException e){
return new Response().failure().message(e.getMessage());
}
}
</code>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
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.