121

我们有一个测试套件,主要使用带有 Hamcrest 匹配器的 JUnit 断言。我们的一个团队开始尝试AssertJ,并以其语法、灵活性和声明性给人们留下了深刻的印象。JUnit 提供了一个我在 AssertJ 中找不到的功能:添加自定义断言失败消息。

我们经常比较那些不是为了人类可读性而制作的对象,它们会有看似随机的 Id 或 UUID,并且不可能通过它们包含的数据来判断它们应该是什么。对于我们的代码库来说,这是一个不可避免的情况,遗憾的是,它实现的部分目的是在其他服务之间映射数据,而不必了解它是什么。

在 JUnit 中,该方法在param之前assertThat提供了一个带有String reason参数的版本。Matcher<T>这使得添加一个简短的调试字符串可以轻松解决问题,例如比较对人类的意义。

另一方面,AssertJ 提供了许多不同的泛型static assertThat方法,它们返回某种形式的接口 Assert或其众多实现类之一。此接口不提供设置自定义消息以包含在故障中的标准方法。

有没有办法从 AssertJ API 或其扩展之一获得此功能,而不必为我们想要添加消息的每个断言类型创建自定义断言类?

4

4 回答 4

162

以经典方式,我在发布问题后立即找到了我正在寻找的东西。希望这将使下一个人更容易找到,而不必先知道它叫什么。魔术方法是具有欺骗性的短名称as,它是另一个AbstractAssert实现的接口的一部分:Descriptable,而不是基本的 Assert 接口。

public S as(String description, Object... args)

设置此对象支持String.format(String, Object...)语法的描述。
例子 :

try {
  // set a bad age to Mr Frodo which is really 33 years old.
  frodo.setAge(50);
  // you can specify a test description with as() method or describedAs(), it supports String format args
  assertThat(frodo.getAge()).as("check %s's age", frodo.getName()).isEqualTo(33);
} catch (AssertionError e) {
  assertThat(e).hasMessage("[check Frodo's age] expected:<[33]> but was:<[50]>");
}

如果断言失败,catch 块中引用的字符串hasMessage是单元测试输出日志中显示的内容。


我通过注意到问题中链接的自定义断言页面failWithMessage中的助手发现了这一点。该方法的JavaDoc指出它是受保护的,因此调用者不能使用它来设置自定义消息。但是它确实提到了助手:as

此外,此方法尊重as(String, Object...)由用户定义的任何描述集或覆盖的错误消息overridingErrorMessage(String, Object...)

... 和覆盖expected: ... but was:...错误消息帮助器,它用提供的新字符串完全替换标准 AssertJ消息。

AssertJ 主页在功能突出显示页面之前没有提及任何一个助手,该页面在软断言as部分显示了助手的示例,但没有直接描述它的作用。

于 2015-03-11T18:41:42.640 回答
43

要为 Patrick M 的答案添加另一个选项:

除了使用Descriptable.as,您还可以使用AbstractAssert.withFailMessage()

try {
  // set a bad age to Mr Frodo which is really 33 years old.
  frodo.setAge(50);
  // you can specify a test description via withFailMessage(), supports String format args
  assertThat(frodo.getAge()).
    withFailMessage("Frodo's age is wrong: %s years, difference %s years",
      frodo.getAge(), frodo.getAge()-33).
    isEqualTo(33);
} catch (AssertionError e) {
  assertThat(e).hasMessage("Frodo's age is wrong: 50 years, difference 17 years");
}

使用的不同之Descriptable.as处在于它使您可以完全控制自定义消息- 没有“预期”和“但是”。

这在被测试的实际值对表示无用的情况下很有用 - 此方法允许您显示其他可能计算的值,或者根本不显示。


请注意,就像 一样Descriptable.as,您必须在任何实际断言withFailMessage() 之前调用- 否则它将不起作用,因为断言将首先触发。这在 Javadoc 中有说明。

于 2019-02-01T11:03:00.530 回答
3

到目前为止提到的两个选项是asand withFailMessage,因此我不再赘述语法或用法。要查看它们之间的区别,以及它们如何有用,请考虑我们正在测试导出指标的用例:

// map of all metrics, keyed by metrics name
Map<String, Double> invocations = ...

List.of(
    "grpc.client.requests.sent",
    "grpc.client.responses.received",
    "grpc.server.requests.received",
    "grpc.server.responses.sent"
).forEach { counter ->
    var meter = // create meter name using counter
    assertThat(invocations)
        .withFailMessage("Meter %s is not found", meter)
        .containsKey(meter)
    assertThat(invocations.get(meter))
        .as(meter)
        .isEqualTo(0.0)
}

我使用 Java 11 语法来减少一些样板。

如果没有withFailMessage,如果地图中不存在仪表,则默认输出包含地图中所有条目的转储,这会使测试日志变得混乱。我们不在乎还有什么其他仪表,只要我们想要的仪表在那里。

使用withFailMessage,输出变为:

java.lang.AssertionError: Meter blah is not found

至于as,它只将给定的消息附加到输出中,但保留失败的比较,这非常有用。我们得到:

org.opentest4j.AssertionFailedError: [blah] 
Expecting:
 <1.0>
to be equal to:
 <0.0>
but was not.
于 2021-03-01T22:51:23.367 回答
3

使用as()AssertJ 中的内置方法。例如:

 assertThat(myTest).as("The test microservice is not active").isEqualTo("active");
于 2020-09-27T21:05:25.760 回答