JUnit5:如何通过单个断言调用断言一个对象的多个属性?

JUnit5: How to assert several properties of an object with a single assert call?

我想通过一次断言调用断言一个对象的多个属性。

使用 JUnit 4 和 Hamcrest 我会写这样的东西:

assertThat(product, allOf(
        hasProperty("name", is("Coat")),
        hasProperty("available", is(true)),
        hasProperty("amount", is(12)),
        hasProperty("price", is(new BigDecimal("88.0")))
));

问:如何使用 JUnit 5 和 AssertJ 在单个断言调用中断言多个属性?或者,在 JUnit 5 世界中,最好的方法是什么。

注意:我当然可以创建一个具有所有需要的属性的对象并执行

assertThat(actualProduct, is(expectedProduct))

但这不是重点。

与 AssertJ 最接近的是:

assertThat(product).extracting("name", "available", "amount", "price")
                   .containsExactly("Coat", true, 12, new BigDecimal("88.0"));

但我不喜欢用 String 引用字段名称,因为它们作为字段名称的有效性仅在运行时检查(已知的反射问题)并且这些 String 可能在我们从 IDE 执行的重构操作期间也被错误地更新。

虽然有点冗长,但我更喜欢:

assertThat(product).extracting(Product::getName, Product::getAvailable, 
                               Product::getAmount, Product::getPrice)
                   .containsExactly("Coat", true, 12, new BigDecimal("88.0"));

您引用的 AssertJ 相对于 Hamcrest 的优势在于它非常流畅:因此在大多数情况下,您需要单个导入:import org.assertj.core.api.Assertions; 并且对于集合断言,有时需要:org.assertj.core.groups.Tuple;

这里是 JUnit 5 或 4;这并不重要,因为对于非常简单的情况,您只会将 JUnit 用作测试运行程序,而让 AssertJ 执行断言。

Or, alternatively, what is the best way of doing that in JUnit 5 universe.

JUnit 5(作为第 4 版)不提供灵活流畅的断言功能。因此,使用 JUnit 5 的最佳方式来做这件事必然会产生更多的样板代码:断言与要断言或覆盖的字段一样多 equals()/hashCode() 出于公平原因要避免的覆盖。

您可以使用 Assertions.assertAll:

assertAll("product",
   () -> assertEquals("Coat", product.getName()),
   () -> assertTrue(product.isAvaikable())
);

assertAll 可以接受任意多的单独断言。这是用户指南部分的 link:https://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions

对于 AssertJ,另一种选择是使用 returns:

assertThat(product)
    .returns("Coat", from(Product::getName)),
    .returns(true, from(Product::getAvailable)),
    .returns(12, from(Product::getAmount)),
    .returns(new BigDecimal("88.0"), from(Product::getPrice));

有点冗长,但我发现它比 extracting/contains.

更容易阅读

请注意,from 只是一个可选的语法糖,用于提高可读性。


从 3.22.0 开始,doesNotReturn 也可用。这对于事先不知道预期值的非空字段很有用。

assertThat(product)
    .returns("Coat", from(Product::getName)),
    .returns(true, from(Product::getAvailable)),
    .doesNotReturn(42, from(Product::getAmount)),
    .doesNotReturn(null, from(Product::getPrice));