Mockito是一个开源框架,用于在Java和Kotlin的单元测试中创建mock对象,它允许将测试代码与外部依赖隔离开来。借助它,开发者可以用具有指定行为的可控替身来替换真实的存储库、API客户端和数据库。根据Mockito.org的数据,该库被超过60%的应用单元测试的Java项目所使用。
要点
Mockito是一个开源库,用于在Java、Kotlin和其他JVM语言的单元测试中创建mock对象(替身)。与负责运行测试的JUnit不同,Mockito解决了隔离问题——将被测试类的真实依赖替换为可预测的对象。
没有mock,测试访问数据库或外部API的方法需要设置真实环境——部署数据库、启动服务器。Mockito用固定行为的对象替换这些依赖:repository.findById(1)方法总是返回特定的User对象,而无需访问数据库。
Mockito的架构基于Proxy模式(用于接口和类)。该库为指定类型生成子类或代理,并拦截所有方法调用,返回默认值或通过when().thenReturn()设置的值。
工作原理 Mockito基于三个基本操作:创建mock、配置行为(stubbing)和验证调用(verification)。每个操作都使用org.mockito.Mockito类中的静态方法——根据Maven Central统计,该类是Java生态系统中加载最多的类。mock的所有方法调用都记录在内存中,以便稍后通过verify进行检查。
典型的Mockito测试包括三个阶段:Arrange — 创建mock并通过when().thenReturn()配置stub,Act — 调用被测试方法,Assert — 通过assertEquals和verify(mock)检查结果。这种方法称为AAA(Arrange-Act-Assert)。
让我们看一个简单的测试,Mockito替换了用户存储库。when().thenReturn()方法配置mock,使findById调用返回一个预先准备好的User对象。
// 我们创建存储库的mock
UserRepository mockRepo = mock(UserRepository.class);
// 配置行为:当findById(1)时返回用户
when(mockRepo.findById(1)).thenReturn(new User("Alice"));
// 检查方法是否确实被调用
User result = mockRepo.findById(1);
assertEquals("Alice", result.getName());
verify(mockRepo).findById(1);
Mockito提供了两种创建mock的方式:静态方法mock(Class)和通过MockitoAnnotations.openMocks()初始化的@Mock注解。第一种方式对于一两个mock来说很简洁,第二种方式在依赖较多时很方便——注解减少了样板代码。
mock()方法接受一个类并返回一个可以通过when().thenReturn()配置的替身对象。所有未配置的方法返回默认值:数字返回0,boolean返回false,对象返回null。
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);
@Mock注解与@ExtendWith(MockitoExtension.class)结合,会自动为测试类的所有字段创建mock。MockitoExtension扩展负责在每个测试之前进行初始化。
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void getUserShouldReturnUserFromRepo() {
when(userRepository.findById(1)).thenReturn(new User("Alice"));
User result = userService.getUser(1);
assertEquals("Alice", result.getName());
}
}
Stubbing — 确定mock方法在使用特定参数调用时应返回什么的过程。基本语法:when(mock.method(args)).thenReturn(value)。针对不同的场景,Mockito提供了几种then方法的变体。
| 方法 | 用途 |
|---|---|
| thenReturn(value) | 始终返回指定的值 |
| thenThrow(exception) | 调用时抛出异常 |
| thenAnswer(answer) | 动态计算返回值 |
| thenCallRealMethod() | 调用真实方法(部分mock) |
当返回值依赖于调用参数时,使用带lambda的thenAnswer。这对于模拟与真实数据的工作很有用——例如,基于传递的对象生成ID。
when(repository.save(any())).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(42);
return user;
});
Verify是Mockito的独特功能,较旧的mock对象库(EasyMock、jMock)不提供此功能。
Verify使测试更加可靠,因为它不仅检查返回值,还检查副作用——不返回结果的方法(void方法)的调用。verify(mock).methodName(args)方法检查mock的特定方法是否使用指定参数被调用。这不仅允许测试结果,还允许测试过程——访问依赖的事实。
默认情况下,verify检查方法是否恰好被调用一次。如果需要不同的次数——使用times(n)、atLeast(n)、never()以及Mockito类中的其他修饰符。
// 检查调用次数
verify(repository, times(3)).save(any());
verify(repository, never()).delete(any());
verify(repository, atLeastOnce()).findById(1);
// 检查调用顺序
InOrder inOrder = inOrder(repository);
inOrder.verify(repository).save(any());
inOrder.verify(repository).flush();
当需要检查方法是用哪个确切对象调用时,使用ArgumentCaptor。它在调用期间捕获参数的值,并允许单独检查其字段。当被测试代码在内部创建对象并将其传递给依赖项时,ArgumentCaptor特别有用——您无法通过其他方式检查此对象。
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());
@Mock和@InjectMocks — Mockito的两个关键注解,显著减少了样板代码。@Mock为字段创建mock,@InjectMocks通过构造函数、setter或字段将测试类中的所有mock注入到被测试对象中。
@InjectMocks机制尝试按以下顺序注入依赖:参数最多的构造函数、按类型的setter、私有字段。如果没有任何方式成功——对象保持null依赖,测试将因NullPointerException失败。
重要的是要理解:@InjectMocks不分析字段类型——它插入任何类型兼容的mock。如果类中有两个相同类型的字段——Mockito可能注入错误的mock。在这种情况下,建议使用带有显式传递mock的构造函数。
在Android开发中,Mockito与JUnit一起用于测试ViewModel、Repository和UseCase。由于这些类在没有Android上下文的JVM上运行,Mockito将它们的依赖——Room DAO、Retrofit API、SharedPreferences——替换为具有可预测行为的stub。
要将Mockito添加到Android项目,只需添加mockito-core或mockito-inline依赖(后者支持mock final类和静态方法)。5.12.0版本(2024)包括对Java 21的支持和与JUnit 5的改进集成。
// build.gradle.kts(模块)
dependencies {
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}
以前,mock静态方法和构造函数需要PowerMock — 一个通过字节码工具工作的扩展。从带有mockito-inline的Mockito 5.x开始,此功能已直接内置:mockStatic(ClassName.class)无需额外库即可mock静态方法。
典型场景:ViewModel调用存储库方法并将结果转换为UI状态。Mockito替换存储库,测试检查ViewModel是否正确处理成功响应和错误。在使用Clean Architecture时,为每一层创建mock:DataSource、Repository和UseCase — 这允许独立测试每一层。
常见问题
Mockito是用于Java和Kotlin的库,使用代理和反射。MockK是Kotlin-first库,支持协程、扩展函数和final类,无需额外配置。
从Mockito 2.1开始,通过opt-in支持mock final类。在5.x版本(mockito-inline)中,默认情况下已启用。只需添加mockito-inline依赖并使用标准的mock()方法即可。
Spy是一种部分mock,默认调用真实方法,但允许通过when().thenReturn()覆盖其中一些方法。Spy对于测试无法重写整个类的遗留代码很有用。
thenReturn无论参数如何,总是返回相同的值。thenAnswer根据调用——参数、mock本身、状态——计算返回值。对于动态响应,始终使用thenAnswer。
Verify不仅检查结果,还检查过程——访问依赖的事实。这对于需要保存数据或发送通知的服务至关重要。没有verify,测试将无法检测到方法未调用save()或send()。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。