Mastering Java Unit Testing: A Deep Dive into Mockito & PowerMock
This comprehensive guide walks you through writing effective Java unit tests, covering test framework basics, Maven dependencies, typical code examples, step‑by‑step test case creation, mocking techniques with Mockito and PowerMock, verification strategies, handling special cases, and best practices for achieving high coverage.
Preface
Inspired by the Qing‑dynasty thinker Zhang Xuecheng, the author emphasizes the importance of deep focus in technology and shares experiences of achieving 100% line coverage in a recent project.
1 Writing Unit Test Cases
1.1 Test Framework Overview
Mockito is a lightweight mocking framework that isolates the class under test from its dependencies. PowerMock extends Mockito to mock static, private, final, and constructor methods, but its use can affect JaCoCo coverage.
The author recommends using Mockito first and only resorting to PowerMock when necessary, avoiding PowerMock features that interfere with coverage.
1.2 Adding Test Framework Dependencies
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>${powermock.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>PowerMock already bundles Mockito and JUnit, so separate Mockito/JUnit dependencies are unnecessary.
1.3 Typical Code Example
/**
* User service class
*/
@Service
public class UserService {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** ID generator */
@Autowired
private IdGenerator idGenerator;
@Value("${userService.canModify}")
private Boolean canModify;
/** Create user */
public Long createUser(UserVO userCreate) {
// Get user ID
Long userId = userDAO.getIdByName(userCreate.getName());
if (Objects.isNull(userId)) {
userId = idGenerator.next();
UserDO create = new UserDO();
create.setId(userId);
create.setName(userCreate.getName());
userDAO.create(create);
} else if (Boolean.TRUE.equals(canModify)) {
UserDO modify = new UserDO();
modify.setId(userId);
modify.setName(userCreate.getName());
userDAO.modify(modify);
} else {
throw new UnsupportedOperationException("不支持修改");
}
return userId;
}
}1.4 Test Case Implementation
/**
* User service test class
*/
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@Mock
private UserDAO userDAO;
@Mock
private IdGenerator idGenerator;
@InjectMocks
private UserService userService;
@Before
public void beforeTest() {
Whitebox.setInternalState(userService, "canModify", Boolean.TRUE);
}
@Test
public void testCreateUserWithNew() {
Mockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());
Long userId = 1L;
Mockito.doReturn(userId).when(idGenerator).next();
String text = ResourceHelper.getResourceAsString(getClass(), "userCreateVO.json");
UserVO userCreate = JSON.parseObject(text, UserVO.class);
Assert.assertEquals("用户标识不一致", userId, userService.createUser(userCreate));
ArgumentCaptor<UserDO> userCreateCaptor = ArgumentCaptor.forClass(UserDO.class);
Mockito.verify(userDAO).create(userCreateCaptor.capture());
String expected = ResourceHelper.getResourceAsString(getClass(), "userCreateDO.json");
Assert.assertEquals("用户创建不一致", expected, JSON.toJSONString(userCreateCaptor.getValue()));
Mockito.verifyNoMoreInteractions(idGenerator, userDAO);
}
@Test
public void testCreateUserWithOld() {
Long userId = 1L;
Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());
String text = ResourceHelper.getResourceAsString(getClass(), "userCreateVO.json");
UserVO userCreate = JSON.parseObject(text, UserVO.class);
Assert.assertEquals("用户标识不一致", userId, userService.createUser(userCreate));
ArgumentCaptor<UserDO> userModifyCaptor = ArgumentCaptor.forClass(UserDO.class);
Mockito.verify(userDAO).modify(userModifyCaptor.capture());
String expected = ResourceHelper.getResourceAsString(getClass(), "userModifyDO.json");
Assert.assertEquals("用户修改不一致", expected, JSON.toJSONString(userModifyCaptor.getValue()));
Mockito.verifyNoInteractions(idGenerator);
Mockito.verifyNoMoreInteractions(userDAO);
}
@Test
public void testCreateUserWithException() {
Whitebox.setInternalState(userService, "canModify", Boolean.FALSE);
Long userId = 1L;
Mockito.doReturn(userId).when(userDAO).getIdByName(Mockito.anyString());
String text = ResourceHelper.getResourceAsString(getClass(), "userCreateVO.json");
UserVO userCreate = JSON.parseObject(text, UserVO.class);
UnsupportedOperationException exception = Assert.assertThrows(
"返回异常不一致",
UnsupportedOperationException.class,
() -> userService.createUser(userCreate));
Assert.assertEquals("异常消息不一致", "不支持修改", exception.getMessage());
}
}1.5 JSON Test Data
{"name":"test"} {"id":1,"name":"test"}2 Test Case Writing Process
The process consists of four stages: defining the object under test, mocking dependencies, invoking the method, and verifying behavior.
2.1 Defining the Object Under Test
Options include direct construction, Mockito.spy, and @Spy annotation.
UserService userService = new UserService();
UserService userService = Mockito.spy(new UserService());
UserService userService = Mockito.spy(UserService.class);2.2 Mocking Dependency Objects
Use @Mock, Mockito.mock, or PowerMock for static/final/private methods.
@Mock private UserDAO userDAO;
@Mock private IdGenerator idGenerator;2.3 Injecting Dependencies
Inject via setters, ReflectionTestUtils.setField, Whitebox.setInternalState, or @InjectMocks.
userService.setUserDAO(userDAO);
ReflectionTestUtils.setField(userService, "maxCount", 100);
Whitebox.setInternalState(userService, "maxCount", 100);2.4 Mocking Dependency Methods
Demonstrates return values, exceptions, argument matchers, and special method mocking (final, private, static, constructors).
// Return value
Mockito.doReturn(user).when(userDAO).get(userId);
// Throw exception
Mockito.doThrow(PersistenceException.class).when(userDAO).get(Mockito.anyLong());
// Mock static method
PowerMockito.mockStatic(HttpHelper.class);
PowerMockito.when(HttpHelper.httpPost(SERVER_URL)).thenReturn(response);2.5 Invoking the Method Under Test
Shows how to call public, protected, package‑private, and private methods using direct calls or Whitebox.invokeMethod.
userService.deleteUser(userId);
User user = (User)Whitebox.invokeMethod(userService, "isSuper", userId);2.6 Verifying Dependency Calls
Uses Mockito.verify with argument matchers, call counts, ArgumentCaptor, and special verifications for final, private, static, and constructor methods.
Mockito.verify(userDAO).delete(userId);
Mockito.verify(userDAO, Mockito.times(2)).delete(Mockito.anyLong());
ArgumentCaptor<UserDO> captor = ArgumentCaptor.forClass(UserDO.class);
Mockito.verify(userDAO).create(captor.capture());
PowerMockito.verifyStatic(StringUtils.class);
StringUtils.isEmpty(str);2.7 Validating Data Objects
Uses JUnit assertions to check nullity, booleans, reference equality, value equality, and complex bean fields.
Assert.assertNull("用户标识必须为空", userId);
Assert.assertTrue("返回值必须为真", NumberHelper.isPositive(1));
Assert.assertEquals("用户名称不一致", "admin", userName);
Assert.assertEquals("用户标识不一致", Long.valueOf(1L), user.getId());2.8 Exception Verification
Shows three ways: @Test(expected=...), @Rule ExpectedException, and Assert.assertThrows.
@Test(expected = ExampleException.class)
public void testGetUser() { ... }
@Rule public ExpectedException exception = ExpectedException.none();
exception.expect(ExampleException.class);
exception.expectMessage("用户不存在");
ExampleException ex = Assert.assertThrows("异常类型不一致", ExampleException.class, () -> userService.getUser(id));
Assert.assertEquals("异常消息不一致", "处理异常", ex.getMessage());2.9 Verifying No Interactions
Mockito.verifyNoInteractions(idGenerator, userDAO);
Mockito.verifyNoMoreInteractions(idGenerator, userDAO);
Mockito.clearInvocations(idGenerator, userDAO);3 Typical Cases and Solutions
3.1 Test Framework Limitations
Lists common pitfalls such as inability of Mockito to mock static/final/private methods, parameter matcher limitations, and the need for @PrepareForTest with PowerMock.
3.2 Capturing Modified Arguments
When a captured list is cleared after verification, the captured reference becomes empty. Solution: capture values inside the mock using doAnswer or store them in a separate collection.
Mockito.doAnswer(invocation -> {
dataList.addAll(invocation.getArgument(0));
return true;
}).when(dataStorage).test(Mockito.anyList());3.3 Mocking Lombok @Slf4j Logger
Static final logger cannot be injected with @InjectMocks. Use a helper to set the static field.
FieldHelper.setStaticFinalField(ExampleService.class, "log", log);3.4 Pandora Container Compatibility
PowerMock’s custom classloader conflicts with Alibaba’s Pandora container. Replace PowerMockRunner with PandoraBootRunner and initialize Mockito annotations manually.
@RunWith(PandoraBootRunner.class)
@Before
public void beforeTest() {
MockitoAnnotations.initMocks(this);
}3.5 Eliminating Unchecked Cast Warnings
Provides nine strategies: using annotations, temporary classes, CastUtils.cast, generic methods, doReturn‑when, Whitebox.invokeMethod, instanceof, Class.cast, and avoiding unnecessary casts.
// Annotation
@Mock private Map<Long, String> resultMap;
@Captor private ArgumentCaptor<Map<String, Object>> parameterMapCaptor;
// Temporary class
private interface DataParser extends Function<Record, Object> {}
Function<Record, Object> dataParser = Mockito.mock(DataParser.class);
// CastUtils
Function<Record, Object> parser = CastUtils.cast(Mockito.mock(Function.class));
// doReturn‑when
List<?> valueList = Mockito.mock(List.class);
Mockito.doReturn(valueList).when(listOperations).range(KEY, start, end);
// Whitebox
Map<Long, Double> scoreMap = Whitebox.invokeMethod(userService, "getScoreMap");
// instanceof
if (obj instanceof JSONArray) {
JSONArray arr = (JSONArray) obj;
}
// Class.cast
if (Objects.equals(clazz, String.class)) {
return clazz.cast(text);
}Conclusion
Effective unit testing in Java requires a solid understanding of mocking frameworks, careful handling of static/final/private members, and disciplined verification. By following the outlined process and applying the presented solutions, developers can achieve high test coverage and maintainable test suites.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
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.
