1. 概述
BDD(行为驱动开发)术语最早由Dan North - 2006年提出。
BDD鼓励使用自然、易读的人类语言编写测试,专注于应用程序的行为。它定义了一种清晰的结构化测试编写方式,遵循三个部分(设定、行动、断言):
- 给定 一些前置条件(设定)
- 当 一个动作发生时(行动)
- 然后 验证输出(断言)
Mockito库随附了一个名为BDDMockito的类,提供了对BDD友好的API。 这个API允许我们采用更符合BDD风格的方式,在使用*given()进行设定和使用then()*进行断言。
在这篇文章中,我们将解释如何设置基于BDD的Mockito测试。我们还将讨论Mockito
和BDDMockito
API之间的差异,最终聚焦于BDDMockito
API。
2. 设置
2.1. Maven依赖
BDD版本的Mockito是mockito-core
库的一部分,为了开始,我们只需要添加以下依赖:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
</dependency>
有关Mockito的最新版本,请查看Maven中央仓库。
2.2. 导入
我们的测试可以通过包含以下静态导入来变得更易读:
import static org.mockito.BDDMockito.*;
请注意,BDDMockito
扩展了Mockito
,因此我们不会错过传统Mockito
API提供的任何功能。
3. Mockito与BDDMockito
在Mockito中,传统的模拟通常在设定步骤中使用when(obj).then\*()
。
随后,我们可以在断言步骤中使用verify()
验证与模拟对象的交互。
BDDMockito
为Mockito
的各种方法提供了BDD别名,因此我们可以使用given
(而不是when
)编写设定步骤,同样,我们可以使用then
(而不是verify
)编写断言步骤。
让我们看看使用传统Mockito编写的测试体示例:
when(phoneBookRepository.contains(momContactName))
.thenReturn(false);
phoneBookService.register(momContactName, momPhoneNumber);
verify(phoneBookRepository)
.insert(momContactName, momPhoneNumber);
现在来看看BDDMockito
是如何比较的:
given(phoneBookRepository.contains(momContactName))
.willReturn(false);
phoneBookService.register(momContactName, momPhoneNumber);
then(phoneBookRepository)
.should()
.insert(momContactName, momPhoneNumber);
4. 使用BDDMockito进行模拟
让我们尝试测试PhoneBookService
,其中我们需要模拟PhoneBookRepository:
public class PhoneBookService {
private PhoneBookRepository phoneBookRepository;
public void register(String name, String phone) {
if(!name.isEmpty() && !phone.isEmpty()
&& !phoneBookRepository.contains(name)) {
phoneBookRepository.insert(name, phone);
}
}
public String search(String name) {
if(!name.isEmpty() && phoneBookRepository.contains(name)) {
return phoneBookRepository.getPhoneNumberByContactName(name);
}
return null;
}
}
BDDMockito
像Mockito
一样,允许我们返回固定或动态值,甚至可以抛出异常。
4.1. 返回固定值
使用BDDMockito
,我们可以轻松配置Mockito,使其在调用模拟对象目标方法时始终返回固定结果:
given(phoneBookRepository.contains(momContactName))
.willReturn(false);
phoneBookService.register(xContactName, "");
then(phoneBookRepository)
.should(never())
.insert(momContactName, momPhoneNumber);
4.2. 返回动态值
BDDMockito
提供了更复杂的方式来返回值。我们可以根据输入返回动态结果:
given(phoneBookRepository.contains(momContactName))
.willReturn(true);
given(phoneBookRepository.getPhoneNumberByContactName(momContactName))
.will((InvocationOnMock invocation) ->
invocation.getArgument(0).equals(momContactName)
? momPhoneNumber
: null);
phoneBookService.search(momContactName);
then(phoneBookRepository)
.should()
.getPhoneNumberByContactName(momContactName);
4.3. 抛出异常
告诉Mockito抛出异常相当直接:
given(phoneBookRepository.contains(xContactName))
.willReturn(false);
willThrow(new RuntimeException())
.given(phoneBookRepository)
.insert(any(String.class), eq(tooLongPhoneNumber));
try {
phoneBookService.register(xContactName, tooLongPhoneNumber);
fail("Should throw exception");
} catch (RuntimeException ex) { }
then(phoneBookRepository)
.should(never())
.insert(momContactName, tooLongPhoneNumber);
注意,我们将given
和will\*
的位置交换了,这是必要的,因为我们在模拟没有返回值的方法。
同时注意,我们使用了像any
、eq
这样的参数匹配器,以提供更通用的基于条件的模拟方式,而不是依赖于固定值。
5. 总结
在这篇快速教程中,我们讨论了BDDMockito
如何将BDD的风格融入到我们的Mockito测试中,并探讨了Mockito
和BDDMockito
之间的一些差异。
如往常一样,源代码可以在GitHub上的com.baeldung.bddmockito
测试包中找到。