第一部分:Mockito的深度掌控
1.1 基础Mock与Stubbing
// 使用Mockito模拟依赖
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService = new UserService();
@Test
void testGetUser() {
// 创建Mock对象并设置Stub
User mockUser = new User(1L, "John Doe");
when(userRepository.findById(1L)).thenReturn(mockUser);
// 执行被测方法
User result = userService.getUser(1L);
// 验证方法调用
verify(userRepository).findById(1L);
assertEquals(mockUser, result);
}
}
关键设计:
@Mock
自动生成Mock对象@InjectMocks
自动注入依赖when().thenReturn()
实现Stubbingverify()
确保方法被正确调用
1.2 高级Mock技巧
// 处理复杂场景:参数匹配器、异常、动态返回值
@Test
void testDynamicStubbing() {
// 使用参数匹配器匹配任意参数
when(userRepository.save(any(User.class))).thenReturn(mockUser);
// 模拟异常
doThrow(new RuntimeException("Mocked Exception"))
.when(userRepository).deleteById(999L);
// 动态返回值
when(userRepository.findByName(anyString()))
.thenAnswer(invocation -> {
String name = (String) invocation.getArguments()[0];
return new User(0L, name + "-mocked");
});
}
核心机制:
any()
匹配任意参数类型doThrow()
分离执行异常与StubbingthenAnswer()
实现动态逻辑doReturn()
绕过Stubbing链
1.3 验证与Mock的边界
// 验证方法调用次数和顺序
@Test
void testOrderAndTimes() {
userService.updateUser(1L, "New Name");
// 验证save()被调用1次
verify(userRepository, times(1)).save(any(User.class));
// 验证方法调用顺序
verify(userRepository, inOrder(userRepository)).findById(1L);
verify(userRepository, inOrder(userRepository)).save(any(User.class));
}
// 使用Spy实现部分Mock
@Test
void testPartialMock() {
// 创建半Mock对象
UserRepository spyRepo = spy(new UserRepositoryImpl());
// 模拟特定方法
when(spyRepo.existsById(1L)).thenReturn(true);
// 调用真实方法
boolean exists = spyRepo.existsById(2L); // 执行真实逻辑
}
高级技巧:
times()
/atLeast()
/never()
控制调用次数inOrder()
确保方法调用顺序spy()
实现混合Mock与真实行为
第二部分:PowerMock的极限突破
2.1 攻克Mockito的禁区
// 模拟静态方法(Mockito无法直接处理)
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class, Util.class})
public class StaticMethodTest {
@Test
public void testStaticMethod() throws Exception {
// 模拟静态方法
PowerMockito.spy(Util.class);
PowerMockito.when(Util.getStaticValue()).thenReturn("Mocked Value");
// 调用包含静态方法的业务逻辑
String result = UserService.generateReport();
// 验证静态方法被调用
PowerMockito.verifyStatic();
Util.getStaticValue();
}
}
突破点:
@RunWith(PowerMockRunner.class)
替换JUnit默认Runner@PrepareForTest
指定需要Mock的类PowerMockito.spy()
与when()
组合使用
2.2 处理构造函数与Final类
// Mock final类(如Java 9+的Collection)
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class FinalClassTest {
@Test
public void testFinalClass() throws Exception {
// 模拟final类的实例
List<User> mockList = PowerMockito.mock(ArrayList.class);
PowerMockito.whenNew(ArrayList.class).withNoArguments().thenReturn(mockList);
// 调用包含new ArrayList()的代码
List<User> result = userService.getUsers();
// 验证构造函数被调用
PowerMockito.verifyNew(ArrayList.class).withNoArguments();
}
}
核心技术:
whenNew()
拦截构造函数调用withArguments()
匹配构造参数thenReturn()
指定返回的Mock对象
2.3 模拟私有方法与字段
// 修改私有字段值
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class PrivateFieldTest {
@Test
public void testPrivateField() throws Exception {
// 获取私有字段
Field field = UserService.class.getDeclaredField("privateField");
field.setAccessible(true);
// 直接设置值
field.set(userService, "Mocked Value");
// 验证值被正确设置
assertEquals("Mocked Value", field.get(userService));
}
}
黑科技:
setAccessible(true)
绕过访问权限set()
/get()
直接操作字段invoke()
调用私有方法
第三部分:实战案例与攻防演练
3.1 复杂依赖的Mock体系
// 模拟Spring Data JPA的Repository
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureMockMvc
public class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@MockBean
private CustomJpaRepository customRepo;
@Test
void testCustomQuery() {
// 准备测试数据
User user = new User(1L, "Test");
entityManager.persist(user);
// 模拟自定义查询
when(customRepo.findByName("Test")).thenReturn(user);
// 执行查询并验证
User result = customRepo.findByName("Test");
assertNotNull(result);
}
}
Spring生态整合:
@DataJpaTest
自动配置JPA环境@MockBean
模拟Spring BeanTestEntityManager
简化数据准备
3.2 防御过度Mock的陷阱
// 反模式:Mock所有依赖(错误示例)
@Test
void testOverMocking() {
// Mock了所有外部依赖,导致测试失去意义
when(serviceA.doSomething()).thenReturn("A");
when(serviceB.doAnother()).thenReturn("B");
// ... 其他Mock
// 测试仅验证内部逻辑,而非真实交互
}
// 正确模式:Mock外部依赖,验证核心逻辑
@Test
void testProperMocking() {
// Mock外部服务
when(externalService.fetchData()).thenReturn("Mocked Data");
// 调用核心业务方法
String result = serviceUnderTest.processData();
// 验证内部逻辑正确性
verify(externalService).fetchData();
assertEquals("Processed Data", result);
}
防御原则:
- 只Mock无法控制的外部依赖
- 验证核心业务逻辑而非依赖实现
- 避免Mock被测对象的内部状态
第四部分:深度防御体系构建
4.1 Mock与真实对象的混合测试
// 使用ArgumentCaptor捕获参数
@Test
void testParameterCapture() {
// 捕获传递给save()的参数
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
userService.createUser("New User");
verify(userRepository).save(captor.capture());
User capturedUser = captor.getValue();
assertEquals("New User", capturedUser.getName());
}
参数验证技巧:
ArgumentCaptor
捕获方法参数getValue()
获取最后一次捕获值- 支持泛型和复杂对象
4.2 异常场景的Mock
// 模拟异常传播
@Test
void testExceptionPropagation() {
// 模拟异常并验证处理逻辑
doThrow(new DatabaseException("Mocked DB Error"))
.when(userRepository).save(any(User.class));
assertThrows(BusinessException.class, () -> {
userService.createUser("Test");
});
// 验证异常处理逻辑
verify(errorLogger).logError(any(DatabaseException.class));
}
异常处理验证:
doThrow()
与when()
分离使用assertThrows()
验证异常类型- 验证错误处理逻辑的正确性
第五部分:终极实战:微服务通信Mock
// 模拟FeignClient的远程调用
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = UserController.class)
@Import(FeignClientsConfiguration.class)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private FeignService feignService;
@Test
void testRemoteCall() throws Exception {
// 模拟FeignClient的响应
when(feignService.getRemoteData()).thenReturn("Mocked Response");
// 发送HTTP请求
mockMvc.perform(get("/api/data"))
.andExpect(status().isOk())
.andExpect(content().string("Mocked Response"));
// 验证远程调用
verify(feignService).getRemoteData();
}
}
微服务测试要点:
@WebMvcTest
隔离控制器测试@MockBean
模拟FeignClientMockMvc
模拟HTTP请求- 验证跨服务协作逻辑
结语:Mock的哲学与兵器库
通过本文的深度实践,我们构建了涵盖:
- 基础Mock:Stubbing与验证
- 复杂场景:静态方法/构造函数/私有方法
- 框架整合:Spring与FeignClient
- 防御体系:参数捕获与异常处理
- 实战案例:从单体到微服务