Mockito — 什么是它,关键概念和工作原理

作者: IT Sectr 发布日期: 2026-04-08 阅读时间: 8 分钟

Mockito是一个开源框架,用于在Java和Kotlin的单元测试中创建mock对象,它允许将测试代码与外部依赖隔离开来。借助它,开发者可以用具有指定行为的可控替身来替换真实的存储库、API客户端和数据库。根据Mockito.org的数据,该库被超过60%的应用单元测试的Java项目所使用。

要点

  • Mockito — 用于创建mock对象的库,在测试中替换真实的依赖。
  • Mock — 模拟真实组件行为的替身对象。
  • Stubbing — 配置mock方法调用时的返回值。
  • Verify — 验证方法是否使用特定参数被调用。
  • @InjectMocks — 自动将mock依赖注入到被测试对象中。

什么是Mockito?

Mockito是一个开源库,用于在Java、Kotlin和其他JVM语言的单元测试中创建mock对象(替身)。与负责运行测试的JUnit不同,Mockito解决了隔离问题——将被测试类的真实依赖替换为可预测的对象。

没有mock,测试访问数据库或外部API的方法需要设置真实环境——部署数据库、启动服务器。Mockito用固定行为的对象替换这些依赖:repository.findById(1)方法总是返回特定的User对象,而无需访问数据库。

Mockito的架构基于Proxy模式(用于接口和类)。该库为指定类型生成子类或代理,并拦截所有方法调用,返回默认值或通过when().thenReturn()设置的值。

Mockito如何工作

工作原理 Mockito基于三个基本操作:创建mock、配置行为(stubbing)和验证调用(verification)。每个操作都使用org.mockito.Mockito类中的静态方法——根据Maven Central统计,该类是Java生态系统中加载最多的类。mock的所有方法调用都记录在内存中,以便稍后通过verify进行检查。

使用Mockito进行测试的三个步骤

典型的Mockito测试包括三个阶段:Arrange — 创建mock并通过when().thenReturn()配置stub,Act — 调用被测试方法,Assert — 通过assertEquals和verify(mock)检查结果。这种方法称为AAA(Arrange-Act-Assert)。

使用存储库mock的基本示例

让我们看一个简单的测试,Mockito替换了用户存储库。when().thenReturn()方法配置mock,使findById调用返回一个预先准备好的User对象。

java
// 我们创建存储库的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);

创建mock对象

Mockito提供了两种创建mock的方式:静态方法mock(Class)和通过MockitoAnnotations.openMocks()初始化的@Mock注解。第一种方式对于一两个mock来说很简洁,第二种方式在依赖较多时很方便——注解减少了样板代码。

通过静态方法mock()

mock()方法接受一个类并返回一个可以通过when().thenReturn()配置的替身对象。所有未配置的方法返回默认值:数字返回0,boolean返回false,对象返回null。

java
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);

通过@Mock注解与JUnit 5

@Mock注解与@ExtendWith(MockitoExtension.class)结合,会自动为测试类的所有字段创建mock。MockitoExtension扩展负责在每个测试之前进行初始化。

java
@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行为

Stubbing — 确定mock方法在使用特定参数调用时应返回什么的过程。基本语法:when(mock.method(args)).thenReturn(value)。针对不同的场景,Mockito提供了几种then方法的变体。

方法用途
thenReturn(value)始终返回指定的值
thenThrow(exception)调用时抛出异常
thenAnswer(answer)动态计算返回值
thenCallRealMethod()调用真实方法(部分mock)

通过thenAnswer实现动态响应

当返回值依赖于调用参数时,使用带lambda的thenAnswer。这对于模拟与真实数据的工作很有用——例如,基于传递的对象生成ID。

java
when(repository.save(any())).thenAnswer(invocation -> {
    User user = invocation.getArgument(0);
    user.setId(42);
    return user;
});

Verify:检查与mock的交互

Verify是Mockito的独特功能,较旧的mock对象库(EasyMock、jMock)不提供此功能。

Verify使测试更加可靠,因为它不仅检查返回值,还检查副作用——不返回结果的方法(void方法)的调用。verify(mock).methodName(args)方法检查mock的特定方法是否使用指定参数被调用。这不仅允许测试结果,还允许测试过程——访问依赖的事实。

检查调用次数

默认情况下,verify检查方法是否恰好被调用一次。如果需要不同的次数——使用times(n)atLeast(n)never()以及Mockito类中的其他修饰符。

java
// 检查调用次数
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特别有用——您无法通过其他方式检查此对象。

java
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());

@Mock和@InjectMocks注解

@Mock@InjectMocks — Mockito的两个关键注解,显著减少了样板代码。@Mock为字段创建mock,@InjectMocks通过构造函数、setter或字段将测试类中的所有mock注入到被测试对象中。

@InjectMocks机制尝试按以下顺序注入依赖:参数最多的构造函数、按类型的setter、私有字段。如果没有任何方式成功——对象保持null依赖,测试将因NullPointerException失败。

@InjectMocks的使用规则

重要的是要理解:@InjectMocks不分析字段类型——它插入任何类型兼容的mock。如果类中有两个相同类型的字段——Mockito可能注入错误的mock。在这种情况下,建议使用带有显式传递mock的构造函数。

Mockito在Android项目中的应用

在Android开发中,Mockito与JUnit一起用于测试ViewModel、Repository和UseCase。由于这些类在没有Android上下文的JVM上运行,Mockito将它们的依赖——Room DAO、Retrofit API、SharedPreferences——替换为具有可预测行为的stub。

为Mockito配置Gradle

要将Mockito添加到Android项目,只需添加mockito-coremockito-inline依赖(后者支持mock final类和静态方法)。5.12.0版本(2024)包括对Java 21的支持和与JUnit 5的改进集成。

kotlin
// build.gradle.kts(模块)
dependencies {
    testImplementation("org.mockito:mockito-core:5.12.0")
    testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}

Mockito和PowerMock:过时的做法

以前,mock静态方法和构造函数需要PowerMock — 一个通过字节码工具工作的扩展。从带有mockito-inline的Mockito 5.x开始,此功能已直接内置:mockStatic(ClassName.class)无需额外库即可mock静态方法。

使用Mockito测试ViewModel

典型场景:ViewModel调用存储库方法并将结果转换为UI状态。Mockito替换存储库,测试检查ViewModel是否正确处理成功响应和错误。在使用Clean Architecture时,为每一层创建mock:DataSource、Repository和UseCase — 这允许独立测试每一层。

  • success case — when(repo.getData()).thenReturn(Result.success(data)) → 我们检查 state = Success(data)。
  • error case — when(repo.getData()).thenReturn(Result.error(exception)) → 我们检查 state = Error(message)。
  • loading state — verify 确保 ViewModel 在调用存储库之前设置了 isLoading = true。

常见问题

Mockito与MockK有什么区别?

Mockito是用于Java和Kotlin的库,使用代理和反射。MockK是Kotlin-first库,支持协程、扩展函数和final类,无需额外配置。

如何在Mockito中为final类创建mock?

从Mockito 2.1开始,通过opt-in支持mock final类。在5.x版本(mockito-inline)中,默认情况下已启用。只需添加mockito-inline依赖并使用标准的mock()方法即可。

什么是Mockito中的Spy?

Spy是一种部分mock,默认调用真实方法,但允许通过when().thenReturn()覆盖其中一些方法。Spy对于测试无法重写整个类的遗留代码很有用。

thenReturn和thenAnswer有什么区别?

thenReturn无论参数如何,总是返回相同的值。thenAnswer根据调用——参数、mock本身、状态——计算返回值。对于动态响应,始终使用thenAnswer。

为什么verify在使用mock的测试中很重要?

Verify不仅检查结果,还检查过程——访问依赖的事实。这对于需要保存数据或发送通知的服务至关重要。没有verify,测试将无法检测到方法未调用save()或send()。

总结

  • Mockito — 用于创建mock对象的库,是Java和Kotlin中mock的事实标准。
  • Mock通过mock(Class)或带有MockitoExtension的@Mock注解创建。
  • Stubbing通过when().thenReturn()设置mock方法的行为。
  • Verify检查mock方法是否使用指定参数被调用及其调用次数。
  • @InjectMocks自动将mock注入到被测试对象中。
  • Android集成 — Mockito用于测试ViewModel、Repository和UseCase。
  • ArgumentCaptor捕获调用参数以详细检查对象字段。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读