Mockito 用于为 Java 接口和类创建测试替身。其核心 API 不依赖 JUnit:程序可以在普通的 main 方法中创建 mock、配置行为、调用代码并验证交互。JUnit 集成增加的是测试发现和 mock 生命周期的自动管理,而不是基本的 mock 能力。
不使用 JUnit 的最小 Maven 项目 #
由于 Mockito 使用 test 作用域,请将演示代码放在 src/test/java 下:
mockito-hello/
├── pom.xml
└── src/test/java/com/example/MockitoHello.java<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mockito-hello</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<mockito.version>5.23.0</mockito.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>package com.example;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
interface Greeter {
String greet(String name);
}
public final class MockitoHello {
public static void main(String[] args) {
Greeter greeter = mock(Greeter.class);
when(greeter.greet("Ada")).thenReturn("Hello, Ada");
String actual = greeter.greet("Ada");
if (!"Hello, Ada".equals(actual)) {
throw new AssertionError("unexpected result: " + actual);
}
verify(greeter).greet("Ada");
}
}编译测试类,并使用 Maven Exec Plugin 运行:
mvn test-compile \
org.codehaus.mojo:exec-maven-plugin:3.6.1:java \
-Dexec.mainClass=com.example.MockitoHello \
-Dexec.classpathScope=test整个过程不涉及测试引擎或 @Test 注解。进程会通过 AssertionError 或 Mockito 的验证异常失败。
Mock 与打桩 #
mock(Greeter.class) 返回一个会记录方法调用的对象。未进行打桩的方法会返回 Mockito 的默认值,例如 null、零、false 或受支持的空集合。
普通打桩会将调用放在 when 中:
when(repository.findById(7L)).thenReturn(user);
when(repository.findById(-1L))
.thenThrow(new IllegalArgumentException("negative ID"));
when(sequence.next()).thenReturn(10, 20, 30);连续返回值可以支持有状态协议,但相比很长的打桩链,fake 对象通常能更清晰地表达复杂状态。
do... 系列用于 void 方法,以及在设置阶段执行方法并不安全的情况:
doNothing().when(queue).acknowledge("message-1");
doThrow(new IOException("offline")).when(client).close();
doAnswer(invocation -> invocation.getArgument(0))
.when(normalizer).normalize(anyString());可用形式包括 doReturn、doThrow、doAnswer、doNothing 和 doCallRealMethod。
验证 #
验证会在被测代码执行完毕后检查协作行为:
verify(repository).save(order);
verify(repository, times(2)).findById(7L);
verify(repository, never()).delete(any());
verifyNoInteractions(auditLog);
verifyNoMoreInteractions(repository);verify(mock) 表示 times(1)。只验证属于单元契约的交互,因为检查每一个实现调用会使测试在保持行为不变的重构后失败。
InOrder 可以验证一个或多个 mock 之间要求的调用顺序:
InOrder order = inOrder(transaction, publisher);
order.verify(transaction).commit();
order.verify(publisher).publish(event);示例要求在 commit 之后进行 publication;无关交互不需要进行顺序验证。
参数匹配与捕获 #
Mockito 使用 equals 比较字面参数。匹配器可以放宽或描述这种比较:
when(catalog.find(eq("book"), anyInt())).thenReturn(results);
verify(catalog).find(eq("book"), intThat(limit -> limit > 0));如果一个参数使用匹配器,则同一调用中的每个参数都必须使用匹配器。使用 eq 包装字面值,不要混用匹配器和字面值形式。
ArgumentCaptor 用于检查被测单元创建的值:
ArgumentCaptor<Email> captor = ArgumentCaptor.forClass(Email.class);
verify(sender).send(captor.capture());
assert "ada@example.com".equals(captor.getValue().recipient());应在验证期间进行捕获。在打桩中使用 captor 会隐藏输入条件,并且当方法从未被调用时,可能产生令人困惑的失败。
Answer 与默认行为 #
Answer 根据调用元数据计算结果:
when(cache.compute(anyString())).thenAnswer(invocation -> {
String key = invocation.getArgument(0);
return key.toUpperCase(Locale.ROOT);
});只有在每个未打桩调用都需要特殊语义时,才向 mock 传入默认 answer:
Service service = mock(Service.class, invocation -> {
throw new AssertionError("unstubbed call: " + invocation.getMethod());
});严格的默认行为可以暴露意外调用,但也会替换 Mockito 的标准默认值,因此需要有意地进行打桩。
Spy 与部分 Mock #
除非方法已被打桩,否则 spy 会调用真实方法:
List<String> names = spy(new ArrayList<>());
doReturn(100).when(names).size();推荐使用 doReturn(...).when(spy)...,因为 when(spy.method()).thenReturn(...) 会在设置阶段调用真实方法。创建 spy 时会复制传入对象的状态;它不是持续委托给原始引用的包装器。
部分 mock 会使测试耦合到实现细节。如果可以修改生产代码设计,更窄的接口或提取出的协作者可以提供更强的边界。
注解生命周期 #
Mockito 注解可以减少 fixture 声明:
class ReportFixture {
@Mock ReportRepository repository;
@Spy Formatter formatter = new Formatter();
@Captor ArgumentCaptor<Report> reportCaptor;
@InjectMocks ReportService service;
}没有测试框架集成时,需要显式初始化并关闭带注解的 mock:
var fixture = new ReportFixture();
try (AutoCloseable mocks = MockitoAnnotations.openMocks(fixture)) {
// Exercise fixture.service.
}MockitoAnnotations.initMocks 会立即关闭 mock 上下文,因此已被弃用。过早关闭会破坏依赖生命周期的 mock maker 和作用域 mock。@InjectMocks 为 fixture 提供便利,会执行构造函数、setter 或字段注入;它不会替代依赖注入配置。
当自定义测试框架需要更多控制时,MockitoSession 提供显式生命周期和严格性:
MockitoSession session = Mockito.mockitoSession()
.initMocks(fixture)
.strictness(Strictness.STRICT_STUBS)
.startMocking();
try {
// Exercise the fixture.
} finally {
session.finishMocking();
}严格 stub 会报告未使用的打桩和参数不匹配,从而暴露出已经不再匹配实际执行行为的 fixture 设置。
Final、Static 和构造函数 Mock #
Mockito 5 默认使用 inline mock maker,因此 mockito-core 可以 mock final 类、final 方法和枚举。较旧的 Mockito 版本可能需要之前的 mockito-inline artifact 或显式的 mock-maker 配置。
Static mock 和构造函数 mock 是线程局部的作用域资源,必须关闭:
try (MockedStatic<ClockSource> clock = mockStatic(ClockSource.class)) {
clock.when(ClockSource::now).thenReturn(fixedInstant);
// Exercise code that calls ClockSource.now().
}
try (MockedConstruction<SocketClient> clients =
mockConstruction(SocketClient.class)) {
// Exercise code that constructs SocketClient.
}无法 mock private 方法和 native 方法。JVM 和 mock maker 的限制也适用于部分 JDK 类型和方法。Static 或构造函数 mock 可以为遗留代码提供迁移接缝,但依赖注入能够避免全局调用点耦合。
现代 JVM 限制动态 agent 附加。使用 Mockito inline mock maker 的构建应遵循 Mockito 文档中的 -javaagent 配置,而不要依赖运行时自附加。
JUnit 5 集成 #
JUnit Jupiter 提供测试发现和生命周期回调。将 Jupiter 集成 artifact 与 JUnit 一起添加:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>mockito-junit-jupiter 会传递性地包含 mockito-core。MockitoExtension 会初始化注解字段和参数,应用严格 stub 的生命周期处理,并在每个测试后关闭 mock:
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
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)
class GreetingServiceTest {
@Mock
GreetingRepository repository;
@InjectMocks
GreetingService service;
@Test
void returnsTheStoredGreeting() {
when(repository.findFor("Ada")).thenReturn("Welcome, Ada");
assertEquals("Welcome, Ada", service.greet("Ada"));
verify(repository).findFor("Ada");
}
}方法参数也可以接收 mock:
@Test
void retriesAfterFailure(@Mock RemoteClient client) {
// The parameter mock exists only for this invocation.
}对于 fixture 设置和测试共享的协作者,优先使用字段 mock;对于只属于单个测试的依赖,使用参数 mock。
评论
在 GitHub 上阅读或评论(发表评论需要登录)