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

Viewed 2626

Given the following class...

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

... I need to compare the actual object with an expected object using AssertJ. However the fields id, activationCode and createDate have dynamic value which I can't hard-code into the assertion.

So the following assertion would fail:
assertThat(actualUserAccount).isEqualTo(expectedUserAccount);

Then I found the following which would ignore certain fields:

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

But what I'm actually looking for is to assert for the objects being equal with the following special checks on certain fields:

  • Is there an id with any Long value?
  • Is there an activationCode with any String value?
  • Is there an createDate with any Date value?

Or is there no other way than to write one assertion for each field?

2 Answers

You can specify how to compare certain fields with withEqualsForFields so you could write something like:

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

I'm checking both ids fields since there is no garantee that id1 is the actual id and id2 the expected id.

You could also write a generic method like BiPredicate<A, B> isType(T type) that returns a bipredicate checking both parameters are of the type T (my suggested signature might not work but you get the idea), that would let you write:

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

Here's what isType looks like (I haven't tested it though):

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

Having said that, I would probably not go that way and write additional assertions.

For reference: https://assertj.github.io/doc/#assertj-core-recursive-comparison-comparators

If you want to verify that all the fields have a value you could use hasNoNullFieldsOrProperties, while returns can be used to refine the verification of email (assuming that getters are exposed):

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

If you must enforce the type of the fields, you can chain:

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

If getters are not available, your original example is probably the only option to verify the value of email:

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

But still you can use extracting(String...) instead of extracting(Function...) to enforce the type of the fields:

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

However, these options are not refactoring-friendly.

Related