
junit + mockito 测试中,不应直接 mock 私有方法;正确做法是通过设计可测的公有接口、隔离外部依赖(如 entitymanager),并以行为驱动方式验证公有方法的整体输出。
junit + mockito 测试中,不应直接 mock 私有方法;正确做法是通过设计可测的公有接口、隔离外部依赖(如 entitymanager),并以行为驱动方式验证公有方法的整体输出。
在实际开发中,getUserDetails() 这类公有方法虽内部调用多个私有方法(如 getUserByNativeQuery() 和 getUserDetailsByNativeQuery()),但 Mockito 原生不支持直接 mock 私有方法,也不推荐借助反射或第三方库强行打桩——这会破坏封装性、增加测试脆弱性,并违背“测试行为而非实现”的单元测试原则。
✅ 正确策略是:将私有方法的逻辑视为公有方法的内部实现细节,通过 mock 其所依赖的协作对象(如 EntityManager)来间接控制私有方法的行为。
由于你的私有方法均通过 EntityManager#createNativeQuery() 执行数据库查询,关键在于:
- 将 EntityManager 作为可注入依赖(例如通过构造函数或 setter 注入);
- 在测试中使用 Mockito.mock(EntityManager.class) 创建模拟对象;
- 使用 when(...).thenReturn(...) 预设 createNativeQuery().getResultList() 等链式调用的返回值。
示例代码如下:
// 被测类(需支持依赖注入)
public class UserService {
private final EntityManager entityManager;
public UserService(EntityManager entityManager) {
this.entityManager = entityManager;
}
public List<UserResponseDto> getUserDetails(UserRequestDto request) {
UserDto userDto = getUserByNativeQuery(request);
List<Object[]> listObj = getUserDetailsByNativeQuery(request);
return setUserResponseDto(listObj);
}
private UserDto getUserByNativeQuery(UserRequestDto request) {
Query query = entityManager.createNativeQuery("SELECT * FROM users WHERE id = ?");
query.setParameter(1, request.getUserId());
List<Object[]> results = query.getResultList();
// ... 映射逻辑
return new UserDto();
}
private List<Object[]> getUserDetailsByNativeQuery(UserRequestDto request) {
Query query = entityManager.createNativeQuery("SELECT u.name, a.city FROM users u JOIN addresses a ON u.id = a.user_id WHERE u.id = ?");
query.setParameter(1, request.getUserId());
return query.getResultList(); // ← 此处将被 mock 控制
}
private List<UserResponseDto> setUserResponseDto(List<Object[]> data) {
return data.stream()
.map(row -> new UserResponseDto((String) row[0], (String) row[1]))
.collect(Collectors.toList());
}
}对应测试类:
@Test
void getUserDetails_shouldReturnMappedResponse() {
// Given
UserRequestDto request = new UserRequestDto(123L);
EntityManager emMock = mock(EntityManager.class);
Query queryMock = mock(Query.class);
// 模拟 getUserDetailsByNativeQuery 的查询结果
List<Object[]> mockResults = Arrays.asList(
new Object[]{"Alice", "Beijing"},
new Object[]{"Bob", "Shanghai"}
);
when(emMock.createNativeQuery(anyString())).thenReturn(queryMock);
when(queryMock.getResultList()).thenReturn(mockResults); // 关键:控制私有方法的输出
UserService service = new UserService(emMock);
// When
List<UserResponseDto> result = service.getUserDetails(request);
// Then
assertThat(result).hasSize(2);
assertThat(result.get(0).getName()).isEqualTo("Alice");
assertThat(result.get(0).getCity()).isEqualTo("Beijing");
}⚠️ 注意事项:
- 不要试图 mock getUserByNativeQuery() 本身——它只是 EntityManager 调用的封装,mock EntityManager 即可覆盖其行为;
- 若 setUserResponseDto() 含复杂逻辑,可单独提取为 package-private 方法并直接测试,提升可测性与可维护性;
- 避免在生产代码中为测试而暴露私有方法(如改为 protected 或添加 @VisibleForTesting 注解),除非有明确重构价值;
- 所有数据库交互必须被完全隔离,确保测试不依赖真实 DB,符合单元测试的快速、可靠、独立特性。
总结:真正的可测性源于良好的依赖抽象和职责分离,而非对私有方法的强行干预。聚焦于“输入 → 输出”契约,用 EntityManager 的 mock 控制整个调用链的数据流,即可高效、稳健地完成对 getUserDetails() 的全覆盖验证。

















