使用 AssertJ 将两个对象与某些字段的 "special" 断言进行比较

Comparing two objects with "special" assertions for certain fields with AssertJ

鉴于以下 class...

public class UserAccount {
  private Long id;
  private String email;
  private String activationCode;
  private Date createDate;
}

...我需要使用 AssertJ 将实际对象与预期对象进行比较。但是,idactivationCodecreateDate 字段具有动态值,我无法将其硬编码到断言中。

所以下面的断言会失败:
assertThat(actualUserAccount).isEqualTo(expectedUserAccount);

然后我发现以下内容会忽略某些字段:

assertThat(actualUserAccount)
        .usingRecursiveComparison()
        .ignoringFields("id", "activationCode", "createDate")
        .isEqualTo(expectedUserAccount);

但我实际上要寻找的是通过对某些字段进行以下特殊检查来断言对象相等:

或者除了为每个字段写一个断言之外别无他法?

如果你想验证所有字段都有一个值,你可以使用 hasNoNullFieldsOrProperties, while returns 可以用来改进 email 的验证(假设公开了 getter):

assertThat(actualUserAccount)
  .hasNoNullFieldsOrProperties()
  .returns(expectedUserAccount.getEmail(), from(UserAccount::getEmail));

如果您必须强制字段的类型,您可以链接:

  .extracting(UserAccount::getId, UserAccount::getEmail, UserAccount::getActivationCode, UserAccount::getCreateDate)
  .hasExactlyElementsOfTypes(Long.class, String.class, String.class, Date.class);

如果 getter 不可用,您的原始示例可能是验证 email 值的唯一选项:

assertThat(actualUserAccount)
    .usingRecursiveComparison()
    .ignoringFields("id", "activationCode", "createDate")
    .isEqualTo(expectedUserAccount);

但您仍然可以使用 extracting(String...) 而不是 extracting(Function...) 来强制字段类型:

assertThat(actualUserAccount)
  .extracting("id", "email", "activationCode", "createDate")
  .hasExactlyElementsOfTypes(Long.class, String.class, String.class, Date.class);

但是,这些选项不利于重构。

您可以指定如何将某些字段与 withEqualsForFields 进行比较,这样您就可以这样写:

assertThat(actualUserAccount)
        .usingRecursiveComparison()
        .withEqualsForFields((id1, id2) -> id1 instanceof Long && id2 instanceof Long, "id")
        .isEqualTo(expectedUserAccount);

我正在检查两个 ID 字段,因为无法保证 id1 是实际 ID,id2 是预期 ID。

你也可以写一个像 BiPredicate<A, B> isType(T type) 这样的通用方法,returns 一个双谓词检查两个参数都是 T 类型(我建议的签名可能行不通,但你明白了),那会让你写:

assertThat(actualUserAccount)
        .usingRecursiveComparison()
        .withEqualsForFields(isType(Long.class), "id")
        .withEqualsForFields(isType(String.class), "activationCode")
        .withEqualsForFields(isType(Date.class), "createDate")
        .isEqualTo(expectedUserAccount);

这是 isType 的样子(不过我还没有测试过):

<A, B, T extends Class<?>> BiPredicate<A, B> isType(T type) {
    return (a, b) -> type.isInstance(a) && type.isInstance(b);
}

话虽如此,我可能不会那样做并编写额外的断言。

供参考:https://assertj.github.io/doc/#assertj-core-recursive-comparison-comparators