本文将探讨AssertJ——一个开源的、社区驱动的库,用于在Java测试中编写流式且丰富的断言。本文重点介绍AssertJ核心模块(AssertJ-core)中提供的工具。

2. Maven依赖

要使用AssertJ,需要在你的pom.xml文件中添加以下依赖:

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.26.0</version>
    <scope>test</scope>
</dependency>

这个依赖仅覆盖基本的Java断言。如果你需要使用高级断言,则需要单独添加其他模块。

注意:对于Java 7及更早版本,应使用AssertJ核心版本2.x.x。

最新版本可以在这里找到。

3. 简介

AssertJ提供了一组类和实用方法,让我们能够轻松地为以下内容编写流式且优美的断言:

  • 标准Java
  • Java 8
  • Guava
  • Joda Time
  • Neo4J
  • Swing组件

所有模块的详细列表可在项目网站上找到。

让我们直接从AssertJ文档中看几个例子:

assertThat(frodo)
  .isNotEqualTo(sauron)
  .isIn(fellowshipOfTheRing);

assertThat(frodo.getName())
  .startsWith("Fro")
  .endsWith("do")
  .isEqualToIgnoringCase("frodo");

assertThat(fellowshipOfTheRing)
  .hasSize(9)
  .contains(frodo, sam)
  .doesNotContain(sauron);

以上例子只是冰山一角,但让我们大致了解了使用这个库编写断言的样子。

4. AssertJ实战

本节我们将重点介绍如何设置AssertJ并探索其功能。

4.1 快速开始

将库的jar包加入类路径后,启用断言只需在测试类中添加一个静态导入:

import static org.assertj.core.api.Assertions.*;

4.2 编写断言

编写断言时,总是需要先将你的对象传递给Assertions.assertThat()方法,然后接着写实际的断言。

要记住,与其他一些库不同,下面的代码实际上并没有断言任何内容,并且永远不会导致测试失败:

assertThat(anyRefenceOrValue);

如果你利用IDE的代码补全功能,编写AssertJ断言会变得异常简单,因为它的方法描述性非常强。在IntelliJ IDEA 16中看起来是这样的:

(IDE会根据类型提示数十个上下文方法,且这些方法仅对String类型可用)

让我们详细探索一下这个API,并看一些特定的断言。

4.3 对象断言

对象可以通过多种方式进行比较,以确定两个对象是否相等,或检查对象的字段。

我们来看两种比较两个对象相等性的方法。给定以下两个Dog对象fidofidosClone

public class Dog { 
    private String name; 
    private Float weight;
    
    // 标准getter和setter
}

Dog fido = new Dog("Fido", 5.25);

Dog fidosClone = new Dog("Fido", 5.25);

我们可以用以下断言比较相等性:

assertThat(fido).isEqualTo(fidosClone);

这会失败,因为isEqualTo()比较的是对象引用。如果我们想比较内容,可以使用isEqualToComparingFieldByFieldRecursively(),像这样:

assertThat(fido).isEqualToComparingFieldByFieldRecursively(fidosClone);

在递归字段比较时,FidofidosClone是相等的,因为一个对象的每个字段都与另一个对象的对应字段进行了比较。

还有许多其他断言方法提供不同的方式来比较和约束对象,以及检查和断言它们的字段。要发现所有方法,请参考官方的AbstractObjectAssert文档

4.4 布尔值断言

有一些简单的方法用于真值测试:

  • isTrue()
  • isFalse()

让我们看看它们如何使用:

assertThat("".isEmpty()).isTrue();

4.5 Iterable/数组断言

对于Iterable或数组,有多种方式断言其内容存在。最常见的断言之一是检查Iterable或数组是否包含给定元素:

List<String> list = Arrays.asList("1", "2", "3");

assertThat(list).contains("1");

或者检查列表是否非空:

assertThat(list).isNotEmpty();

或者检查列表是否以给定字符开头,例如“1”:

assertThat(list).startsWith("1");

请记住,如果你想对同一对象进行多个断言,可以轻松地将它们连接起来。

下面是一个断言示例,它检查给定列表:

  • 非空
  • 包含“1”元素
  • 不包含任何null值
  • 包含元素序列“2”、“3”
assertThat(list)
  .isNotEmpty()
  .contains("1")
  .doesNotContainNull()
  .containsSequence("2", "3");

当然,这些类型还有更多可能的断言。要发现所有方法,请参考官方的AbstractIterableAssert文档

4.6 字符断言

字符类型的断言主要涉及比较,甚至检查给定字符是否来自Unicode表。

下面是一个断言示例,它检查给定字符:

  • 不是‘a’
  • 在Unicode表中
  • 大于‘b’
  • 是小写
assertThat(someCharacter)
  .isNotEqualTo('a')
  .inUnicode()
  .isGreaterThanOrEqualTo('b')
  .isLowerCase();

有关所有字符类型断言的详细列表,请参见AbstractCharacterAssert文档

4.7 类断言

Class类型的断言主要是关于检查其字段、类类型、注解的存在以及类的最终性。

如果你想断言Runnable类是一个接口,只需写:

assertThat(Runnable.class).isInterface();

或者检查一个类是否可以从另一个类赋值:

assertThat(Exception.class).isAssignableFrom(NoSuchElementException.class);

所有可能的Class断言可以在AbstractClassAssert文档中查看。

4.8 文件断言

文件断言主要是关于检查给定的File实例:

  • 是否存在
  • 是目录还是文件
  • 是否有特定内容
  • 是否可读
  • 是否有给定的扩展名

这里你可以看到一个断言示例,它检查给定文件:

  • 存在
  • 是文件而非目录
  • 可读
  • 可写
 assertThat(someFile)
   .exists()
   .isFile()
   .canRead()
   .canWrite();

所有可能的文件断言可以在AbstractFileAssert文档中查看。

4.9 Double/Float/Integer断言

Double/Float/Integer和其他数字类型

数值断言主要是关于在给定偏移量内或外比较数值。例如,如果你想根据给定精度检查两个值是否相等,我们可以这样做:

assertThat(5.1).isEqualTo(5, withPrecision(1d));

注意,我们使用已经导入的withPrecision(Double offset)辅助方法来生成Offset对象。

更多断言,请访问AbstractDoubleAssert文档

4.10 InputStream断言

只有一个InputStream特定的断言可用:

  • hasSameContentAs(InputStream expected)

使用示例:

assertThat(given).hasSameContentAs(expected);

4.11 Map断言

Map断言允许你检查map是否:

  • 包含特定条目
  • 包含一组条目
  • 分别包含键/值

这里你可以看到一个断言示例,它检查给定map:

  • 非空
  • 包含数字键“2”
  • 不包含数字键“10”
  • 包含条目:键2,值“a”
assertThat(map)
  .isNotEmpty()
  .containsKey(2)
  .doesNotContainKeys(10)
  .contains(entry(2, "a"));

更多断言,请参见AbstractMapAssert文档

4.12 Throwable断言

Throwable断言允许:

  • 检查异常消息
  • 检查堆栈跟踪
  • 检查原因
  • 验证异常是否已被抛出

让我们看一个断言示例,它检查给定异常:

  • 已被抛出
  • 消息以“c”结尾
assertThat(ex).hasNoCause().hasMessageEndingWith("c");

更多断言,请参见AbstractThrowableAssert文档

5. 为断言消息添加自定义描述

为了实现更高的详细程度,你可以为断言创建动态生成的自定义描述。关键在于使用as(String description, Object… args)方法。

如果你像这样定义断言:

assertThat(person.getAge())
  .as("%s's age should be equal to 100", person.getName())
  .isEqualTo(100);

运行测试时,你会得到:

[Alex's age should be equal to 100] expected:<100> but was:<34>

6. Java 8

AssertJ充分利用了Java 8的函数式编程特性。让我们深入一个例子,看看它的实际应用。首先,我们看看在Java 7中如何做:

assertThat(fellowshipOfTheRing)
  .filteredOn("race", HOBBIT)
  .containsOnly(sam, frodo, pippin, merry);

这里我们根据种族Hobbit过滤集合,在Java 8中我们可以这样做:

assertThat(fellowshipOfTheRing)
  .filteredOn(character -> character.getRace().equals(HOBBIT))
  .containsOnly(sam, frodo, pippin, merry);

我们将在本系列的未来文章中探索AssertJ的Java8功能。以上示例取自AssertJ的网站

7. 结论

本文简要探讨了AssertJ为我们提供的可能性,以及核心Java类型最流行的断言。

所有示例和代码片段的实现可以在GitHub项目中找到。


原始标题:Introduction to AssertJ | Baeldung