2. 接口默认方法的模拟
Java 8 开始允许在接口中定义默认方法实现。这个新特性虽然强大,却打破了 Java 早期设计的核心概念。Mockito 1.x 版本对此毫无准备——它根本不支持调用接口中的真实方法。
假设我们有一个包含两个方法的接口:一个是传统方法签名,另一个是全新的 default
方法:
public interface JobService {
Optional<JobPosition> findCurrentJobPosition(Person person);
default boolean assignJobPosition(Person person, JobPosition jobPosition) {
if(!findCurrentJobPosition(person).isPresent()) {
person.setCurrentJobPosition(jobPosition);
return true;
} else {
return false;
}
}
}
注意 assignJobPosition()
这个默认方法调用了未实现的 findCurrentJobPosition()
方法。
现在我们想测试 assignJobPosition()
的逻辑,但又不想实现 findCurrentJobPosition()
。我们可以这样操作:
- 创建
JobService
的 mock 对象 - 让
findCurrentJobPosition()
返回预设值 - 调用真实的
assignJobPosition()
方法
public class JobServiceUnitTest {
@Mock
private JobService jobService;
@Test
public void givenDefaultMethod_whenCallRealMethod_thenNoExceptionIsRaised() {
Person person = new Person();
when(jobService.findCurrentJobPosition(person))
.thenReturn(Optional.of(new JobPosition()));
doCallRealMethod().when(jobService)
.assignJobPosition(
Mockito.any(Person.class),
Mockito.any(JobPosition.class)
);
assertFalse(jobService.assignJobPosition(person, new JobPosition()));
}
}
✅ 这段代码在抽象类上完全没问题,但在接口上直接翻车。Mockito 1.x 会这样报错:
org.mockito.exceptions.base.MockitoException:
Cannot call a real method on java interface. The interface does not have any implementation!
Calling real methods is only possible when mocking concrete classes.
⚠️ 原因很简单:Java 8 之前,接口不可能有实现方法!Mockito 1.x 的设计基于这个前提。
解决方案:升级到 Mockito 2.x(比如 2.7.5):
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.7.5</version>
<scope>test</scope>
</dependency>
升级后无需修改任何代码,测试直接通过。这就是版本升级带来的红利。
3. Optional 和 Stream 的默认返回值
Optional
和 Stream
是 Java 8 的两大神器。它们都有特殊的"空值"表示(Optional.empty()
和 Stream.empty()
),能大幅减少 NullPointerException
。
3.1. Optional 示例
看这个服务类,它依赖前面定义的 JobService
:
public class UnemploymentServiceImpl implements UnemploymentService {
private JobService jobService;
public UnemploymentServiceImpl(JobService jobService) {
this.jobService = jobService;
}
@Override
public boolean personIsEntitledToUnemploymentSupport(Person person) {
Optional<JobPosition> optional = jobService.findCurrentJobPosition(person);
return !optional.isPresent();
}
}
现在测试"无工作可领失业金"的场景。在 Mockito 1.x 中,我们必须手动 mock 返回空 Optional
:
public class UnemploymentServiceImplUnitTest {
@Mock
private JobService jobService;
@InjectMocks
private UnemploymentServiceImpl unemploymentService;
@Test
public void givenReturnIsOfTypeOptional_whenMocked_thenValueIsEmpty() {
Person person = new Person();
when(jobService.findCurrentJobPosition(any(Person.class)))
.thenReturn(Optional.empty());
assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(person));
}
}
👉 Mockito 1.x 默认返回 null
,而 Optional
最怕 null
!
Mockito 2.x 的改进:默认返回空 Optional
(相当于 Optional.empty()
)。所以测试可以简化为:
public class UnemploymentServiceImplUnitTest {
@Test
public void givenReturnIsOptional_whenDefaultValueIsReturned_thenValueIsEmpty() {
Person person = new Person();
assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(person));
}
}
3.2. Stream 示例
给 JobService
添加一个返回 Stream
的方法:
public interface JobService {
Stream<JobPosition> listJobs(Person person);
}
在服务类中实现职位搜索功能:
public class UnemploymentServiceImpl implements UnemploymentService {
@Override
public Optional<JobPosition> searchJob(Person person, String searchString) {
return jobService.listJobs(person)
.filter((j) -> j.getTitle().contains(searchString))
.findFirst();
}
}
测试"无工作经历"场景时,Mockito 1.x 需要手动 mock:
public class UnemploymentServiceImplUnitTest {
@Test
public void givenReturnIsOfTypeStream_whenMocked_thenValueIsEmpty() {
Person person = new Person();
when(jobService.listJobs(any(Person.class))).thenReturn(Stream.empty());
assertFalse(unemploymentService.searchJob(person, "").isPresent());
}
}
Mockito 2.x 的改进:默认返回空 Stream
(相当于 Stream.empty()
)。测试代码进一步简化:
public class UnemploymentServiceImplUnitTest {
@Test
public void givenReturnIsStream_whenDefaultValueIsReturned_thenValueIsEmpty() {
Person person = new Person();
assertFalse(unemploymentService.searchJob(person, "").isPresent());
}
}
4. Lambda 表达式的高阶玩法
Java 8 的 Lambda 让代码更紧凑。在 Mockito 中,结合 ArgumentMatcher
和自定义 Answer
使用效果拔群。
4.1. Lambda + ArgumentMatcher
Java 8 之前,需要创建实现 ArgumentMatcher
的类。现在用 Lambda 一行搞定:
public class ArgumentMatcherWithLambdaUnitTest {
@Test
public void whenPersonWithJob_thenIsNotEntitled() {
Person peter = new Person("Peter");
Person linda = new Person("Linda");
JobPosition teacher = new JobPosition("Teacher");
when(jobService.findCurrentJobPosition(
ArgumentMatchers.argThat(p -> p.getName().equals("Peter"))))
.thenReturn(Optional.of(teacher));
assertTrue(unemploymentService.personIsEntitledToUnemploymentSupport(linda));
assertFalse(unemploymentService.personIsEntitledToUnemploymentSupport(peter));
}
}
4.2. Lambda + 自定义 Answer
同样,自定义 Answer
也能用 Lambda 简化。比如让 listJobs()
根据人名返回不同结果:
public class CustomAnswerWithLambdaUnitTest {
@Before
public void init() {
when(jobService.listJobs(any(Person.class))).then((i) ->
Stream.of(new JobPosition("Teacher"))
.filter(p -> ((Person) i.getArgument(0)).getName().equals("Peter")));
}
}
👉 完全不需要定义 PersonAnswer
内部类,逻辑直接内联!
5. 总结
Mockito 2.x 对 Java 8 特性的支持让测试代码更简洁:
- 接口默认方法可直接调用真实实现
Optional
/Stream
默认返回空值而非null
- Lambda 表达式大幅简化参数匹配和自定义逻辑
这些改进不仅减少了样板代码,还让测试意图更清晰。如果你还在用 Mockito 1.x,升级吧——这波升级绝对不亏!
延伸阅读:
本文代码已上传至 GitHub 仓库