Contents

BDDMockito 简介

1. 概述

BDD 术语最早是由Dan North 在 2006 年 创造的。 BDD 鼓励使用自然的、人类可读的语言编写测试,该语言专注于应用程序的行为。 它定义了一种结构清晰的测试编写方式,遵循三个部分(Arrange、Act、Assert):

  • *given *一些先决条件(安排)
  • *when *动作发生时(Act)
  • *then *验证输出(断言)

**Mockito 库附带一个BDDMockito类,该类引入了 BDD 友好的 API。这个 API 允许我们采用更 BDD 友好的方法来安排我们的测试,使用*given()并使用then()*进行断言。

在本文中,我们将解释如何设置基于 BDD 的 Mockito 测试。我们还将讨论MockitoBDDMockito 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 模拟

让我们尝试测试我们需要模拟 PhoneBookRepositoryPhoneBookService

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))来提供一种更通用的基于标准而不是依赖于固定值的模拟方式。