单元测试的核心目标是验证单个类或方法的行为,因此需要将其与外部依赖如数据库访问、远程接口调用、第三方服务等隔离开。Mockito框架可以通过创建模拟对象来替代真实依赖,从而控制依赖方法的返回结果,避免触发真实调用。不过如果用法不当,很容易出现模拟失效、真实方法被执行的情况,影响测试的稳定性和执行效率。

Mockito模拟的基础准备
首先需要引入Mockito的依赖,以Maven项目为例,在pom.xml中添加如下依赖:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.0.0</version>
<scope>test</scope>
</dependency>
编写测试类时,通常使用@Mock注解来创建模拟对象,配合MockitoAnnotations.openMocks(this)初始化,或者使用Mockito.mock(Class)方法手动创建模拟对象。以下是一个基础示例:
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
// 待测试的依赖接口
interface UserService {
String getUserNameById(Long id);
}
// 待测试的业务类
class OrderService {
private UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
public String getOrderOwner(Long userId) {
return userService.getUserNameById(userId);
}
}
public class OrderServiceTest {
@Mock
private UserService userService;
private OrderService orderService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
orderService = new OrderService(userService);
}
}
使用when thenReturn模拟有返回值的方法
对于返回值的依赖方法,最常用的模拟方式是when(依赖方法调用).thenReturn(返回值),这种方式会拦截对依赖方法的调用,直接返回指定的结果,不会触发真实调用。示例如下:
@Test
void testGetOrderOwner_WithValidUserId() {
// 模拟userService的getUserNameById方法,当传入1L时返回"张三"
when(userService.getUserNameById(1L)).thenReturn("张三");
// 调用待测试方法
String result = orderService.getOrderOwner(1L);
// 验证结果
assert "张三".equals(result);
// 验证依赖方法确实被调用了一次
verify(userService, times(1)).getUserNameById(1L);
}
需要注意,when后面的方法调用必须是模拟对象的方法,如果是真实对象的方法调用,就会触发真实执行。另外如果传入的参数不匹配模拟时设定的条件,也不会返回模拟的结果,而是返回模拟对象的默认值,比如String类型返回null,int类型返回0。
使用doReturn when模拟避免真实调用
当依赖方法是被监视的对象(spy)的方法,或者方法调用会抛出无法捕获的异常时,使用when thenReturn可能会先触发一次真实调用,此时需要改用doReturn(返回值).when(模拟对象).方法调用的语法,这种写法不会先执行真实方法。示例如下:
@Test
void testGetOrderOwner_WithDoReturn() {
// 使用doReturn模拟,不会触发真实调用
doReturn("李四").when(userService).getUserNameById(2L);
String result = orderService.getOrderOwner(2L);
assert "李四".equals(result);
}
这种方式尤其适合模拟void方法、受检异常方法,或者需要避免方法调用副作用的场景。
模拟void无返回值的方法
对于无返回值的依赖方法,无法使用thenReturn,需要使用doNothing、doThrow等语法来模拟。默认情况下,模拟对象的void方法调用不会执行任何操作,也就是doNothing是默认行为,如果需要模拟抛出异常,可以使用doThrow。示例如下:
interface LogService {
void recordLog(String content);
}
class OrderService {
private LogService logService;
public OrderService(LogService logService) {
this.logService = logService;
}
public void createOrder() {
logService.recordLog("创建订单");
}
}
@Test
void testCreateOrder_WithVoidMethod() {
LogService logService = mock(LogService.class);
OrderService orderService = new OrderService(logService);
// 模拟void方法,默认doNothing,无需额外配置
orderService.createOrder();
// 验证方法被调用
verify(logService).recordLog("创建订单");
}
@Test
void testCreateOrder_WithVoidMethodThrowException() {
LogService logService = mock(LogService.class);
OrderService orderService = new OrderService(logService);
// 模拟void方法抛出异常
doThrow(new RuntimeException("日志写入失败")).when(logService).recordLog(anyString());
// 调用时会抛出异常
try {
orderService.createOrder();
} catch (RuntimeException e) {
assert "日志写入失败".equals(e.getMessage());
}
}
参数匹配规则与避免真实调用
Mockito提供了多种参数匹配器,比如any()、eq()、anyLong()等,使用匹配器时需要注意,如果一个参数使用了匹配器,所有参数都必须使用匹配器,否则会导致模拟失效,甚至触发真实调用。示例如下:
@Test
void testGetOrderOwner_WithMatcher() {
// 正确用法:所有参数都使用匹配器
when(userService.getUserNameById(anyLong())).thenReturn("王五");
String result1 = orderService.getOrderOwner(3L);
assert "王五".equals(result1);
// 错误用法:混合使用具体值和匹配器,会导致模拟不生效
// when(userService.getUserNameById(3L, anyString())).thenReturn("赵六"); // 编译错误或运行时异常
}
如果模拟时使用了过于宽泛的匹配器,比如any(),可能会导致非预期的调用也被匹配到,因此建议尽量使用精确的匹配条件,减少模拟的意外覆盖范围。
验证调用行为而不触发真实逻辑
模拟依赖方法后,通常需要验证依赖方法是否按照预期被调用,此时使用verify方法即可,verify只会检查模拟对象的方法调用记录,不会触发真实调用。可以指定调用的次数、参数等条件:
@Test
void testGetOrderOwner_VerifyCall() {
when(userService.getUserNameById(1L)).thenReturn("张三");
orderService.getOrderOwner(1L);
// 验证方法被调用一次
verify(userService, times(1)).getUserNameById(1L);
// 验证方法没有被调用传入2L的情况
verify(userService, never()).getUserNameById(2L);
}
常见错误与避坑指南
- 不要对真实对象调用
when方法:如果对new出来的真实对象使用when,会直接执行真实方法,需要先将真实对象转为spy对象,或者使用doReturn语法。 - 避免模拟final类、final方法、私有方法:Mockito默认无法模拟这些类型的方法,需要开启对应的扩展功能,否则模拟会失效触发真实调用。
- 模拟配置要在调用待测试方法之前完成:如果先调用了待测试方法,依赖方法已经执行过真实逻辑,再配置模拟也不会生效。
- 不要过度模拟:只模拟待测试类的直接依赖,不要模拟无关的对象,否则会让测试失去对真实逻辑的验证作用。
通过合理使用Mockito的模拟语法,明确区分when thenReturn和doReturn when的适用场景,注意参数匹配规则,就可以有效避免依赖方法的真实调用,写出独立、稳定、高效的单元测试。