BDDMockito 简介
1. 概述
BDD 术语最早是由Dan North 在 2006 年 创造的。 BDD 鼓励使用自然的、人类可读的语言编写测试,该语言专注于应用程序的行为。 它定义了一种结构清晰的测试编写方式,遵循三个部分(Arrange、Act、Assert):
- *given *一些先决条件(安排)
- *when *动作发生时(Act)
- *then *验证输出(断言)
**Mockito 库附带一个BDDMockito类,该类引入了 BDD 友好的 API。这个 API 允许我们采用更 BDD 友好的方法来安排我们的测试,使用*given()并使用then()*进行断言。
在本文中,我们将解释如何设置基于 BDD 的 Mockito 测试。我们还将讨论Mockito和BDDMockito API 之间的差异,最终将重点放在BDDMockito API。
2. 设置
2.1. Maven 依赖项
Mockito 的 BDD 风格是mockito-core库的一部分,为了开始我们只需要包含工件:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.21.0</version>
</dependency>
有关最新版本的 Mockito,请查看Maven Central 。
2.2. 引用
如果我们包含以下静态导入,我们的测试会变得更具可读性:
import static org.mockito.BDDMockito.*;
请注意,BDDMockito扩展了 Mockito,因此我们不会错过传统Mockito API 提供的任何功能。
3. Mockito 与 BDDMockito
Mockito 中的传统模拟是使用*when(obj)*执行的。*then()*在排列步骤中。
稍后,可以在 Assert 步骤中使用*verify()*来验证与我们的 mock 的交互。
BDDMockito为各种Mockito方法提供 BDD 别名,因此我们可以使用given(而不是when)编写 Arrange 步骤,同样,我们可以使用then(而不是verify)编写 Assert 步骤。**
让我们看一个使用传统 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 模拟
让我们尝试测试我们需要模拟 PhoneBookRepository 的PhoneBookService:
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))来提供一种更通用的基于标准而不是依赖于固定值的模拟方式。