您可以向 AssertJ assertThat 添加自定义消息吗?

Can you add a custom message to AssertJ assertThat?

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

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

在 JUnit 中,assertThat 方法在 Matcher<T> 参数之前提供一个带有 String reason 参数的版本。这使得添加一个简短的调试字符串来阐明问题变得微不足道,比如比较对人类意味着什么。

另一方面,

AssertJ 提供了数以亿计的不同 genericized static assertThat methods which return some form of interface Assert 或其众多实现之一 类。此接口不提供设置要包含在故障中的自定义消息的标准方法。

有没有什么方法可以从 AssertJ API 或其扩展之一获得此功能,而不必 create a custom assert class for every assert type 我们要向其中添加消息?

以经典的方式,我在发布问题后立即找到了我要找的东西。希望这将使下一个人更容易找到,而不必先知道它的名字。神奇的方法是具有欺骗性的短名称 as, which is part of another interface that AbstractAssert implements: Descriptable,而不是基本的 Assert 接口。

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

Sets the description of this object supporting String.format(String, Object...) syntax.
Example :

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 是断言失败时出现在单元测试输出日志中的内容。


我通过注意到 custom assert page linked in the question. The JavaDoc 中的 failWithMessage 帮助程序发现该方法指出它是受保护的,因此调用者不能使用它来设置自定义消息。但是它确实提到了 as 助手:

Moreover, this method honors any description set with as(String, Object...) or overridden error message defined by the user with overridingErrorMessage(String, Object...).

... 和 overridingErrorMessage 助手,它用提供的新字符串完全替换了标准的 AssertJ expected: ... but was:... 消息。

AssertJ 主页在功能突出显示页面之前没有提到任何一个帮助程序,该页面在 Soft Assertions 部分显示了 as 帮助程序的示例,但没有直接描述它的作用。

为 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 的不同之处在于,它让您 完全控制自定义消息 - 没有 "expected" 和 "but was" .

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


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

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

 assertThat(myTest).as("The test microservice is not active").isEqualTo("active");

目前提到的两个选项是aswithFailMessage,语法和用法我就不再赘述了。要了解它们之间的区别以及它们各自的用处,请考虑我们正在测试导出指标的用例:

// 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)
}

I've used Java 11 syntax to reduce some boilerplate.

如果没有 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.