Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compare lists of custom classes without defining equals() and hashCode()?

Since defining equals() and hashCode() only for testing purpose is considered as a code smell, I prefer to use ReflectionEquals or custom matchers to compare objects while doing unit testing.

However, I don't know how to use ReflectionEquals or custom matchers in comparing lists of user-defined classes.

For example, how do I assert the following code without defining equals() and hashCode() (maybe only use ReflectionEquals or custom matchers)?

// When
List<Record> actual = sut.findBySomeId();

// Then
List<Record> expected = asList(
    aRecord()...build(), 
    aRecord()...build()
);
assertThat(expected, /* how to compare? */);
like image 821
Fred Pym Avatar asked Feb 22 '17 09:02

Fred Pym


2 Answers

There is a super fluent library for solving your issue, called AssertJ. It is quite easy to use, flexible to make changes and its fluency makes the tests easy to read! In order to use it, first you have to import the right package

import static org.assertj.core.api.Assertions.*;

I do not know how your Record model looks like, so I made a dummy one:

public class Record {
    private String name;
    private String data;
    private String history;

    public Record(String name, String data, String history) {
        this.name = name;
        this.data = data;
        this.history = history;
    }

    public String getName() {
        return name;
    }

    public String getData() {
        return data;
    }

    public String getHistory() {
        return history;
    }

}

Then the test would like like, if you only want to assert list of Record based on one field:

@Test
public void give_when_then() throws Exception {
    List<Record> actualList = sut.findBySomeId();

    assertThat(actualList)
            .extracting(record -> record.getData())
            .containsExactly("data1", "data2", "data3");
}

If you want to assert the objects based on multiple fields, you can do for example:

@Test
public void give_when_then() throws Exception {
    List<Record> actualList = sut.findBySomeId();

    assertThat(actualList)
            .extracting(
                    record -> record.getName(),
                    record -> record.getHistory())
            .containsExactly(
                    tuple("name1", "history1"),
                    tuple("name2", "history2"),
                    tuple("name3", "history3"));
}

...where tuple is also an assert4J object for wrapping the results.

Furthermore there are bunch of assert methods like containsExactlyInAnyOrder(...), containsAll(...), containsAnyOf(...) etc., which could also help your life in certain cases.

Last but not least you can also write your own specific object assert class extending AbstractObjectAssert base class. With this you will have (among others) a method called isEqualToComparingFieldByField. From the official documentation:

Assert that actual object is equal to the given object based on a property/field by property/field comparison (including inherited ones). This can be handy if equals implementation of objects to compare does not suit you.

First you define your own assert class:

public class RecordAssert extends AbstractObjectAssert<RecordAssert, Record> {

    public RecordAssert(Record actual) {
        super(actual, RecordAssert.class);
    }

}

Then the usage of it would look like:

@Test
public void give_when_then() throws Exception {
    List<Record> actual = sut.findBySomeId();

    assertThat(actual.get(0)).isEqualToComparingFieldByField(new Record("name1", "data1", "history1"));
    // Asserts for other objects

}

I hope it helps!

like image 173
mirind4 Avatar answered Nov 14 '22 22:11

mirind4


The Hamcrest library has a great selection of matchers for making assertions on collections types. In particular, the hasItem, hasItems, contains and containsAnyOrder matchers as these can use matchers themselves (I like to use TypeSafeMatcher) to test the items in the collections.

I'll leave you to decide which one best suits your needs, but I'll use contains for my example:

List<Record> actual = sut.findBySomeId();

Record expected1 = aRecord()...build();
Record expected2 = aRecord()...build();

assertThat(actual, contains(matchingRecord(expected1), matchingRecord(expected2));

...

// somewhere the test has access to it
private Matcher<Record> matchingRecord(Record expected) {
    return new TypeSafeMatcher<Record>() {
        public boolean matchesSafely(Record actual) {
            // perform tests and return result, e.g.
            return actual.getValue() == expected.getValue();
        }

        public void describeMismatchSafely(Record record, Description mismatchDescription) {
            mismatchDescription.appendText("give a meaningful message");
        }
    };

}
like image 36
Michael Peyper Avatar answered Nov 14 '22 21:11

Michael Peyper