Nephthys76
Nephthys76

Reputation: 655

Hamcrest Collection matching where objects in list have a field that matches values in another list

I have a method under test which is returning a list of objects... such as a "Person" object.

I have a list of "expectedLastNames" to validate the result against.

I currently have a working test that loops through the names in "expectedLastNames" and asserts that each is contained in the list of "Person" objects. Like so (be advised the following code snippet is in Kotlin):

expectedLastNames.forEach { expectedLastName ->
    assertThat(listOfPeople, hasItem(hasProperty("lastName", `is`(expectedLastName.trim()))))
}

This works great when the assertion passes, and validates my method. However, it is extremely cumbersome when the test fails, because it fast-fails as soon as it encounters a name that is missing and doesn't assert the rest of the names. I would prefer to improve my assertion to report ALL missing names all at once instead of one at a time.

Something along the lines of:

assertThat(expectedLastNames, allItems(lastNameMatches(listOfPeople)));

I want my assertionFailure message to be something like:

Expected a list matching all of "Smith, Jones, Davis", but ["Smith", "Davis"] were not found in "Person[firstName=John, lastName="Jones"], Person[firstName=Sarah, lastName=Jones]"

If there are additional Person objects in the results with lastName values that are not included in "expectedLastNames", this should still pass as long as ALL of the names in "expectedLastNames" are represented in the results.

It is also important to know that this is a Junit5 parameterized test, and the "expectedLastNames" is one of the parameters that are being passed in. So I cannot just hard-code the last names into the assertion.

Is there a Hamcrest collection matcher that will do what I want? Or if not, can anybody get me started with a custom matcher that will do it?

Any help is appreciated.

Upvotes: 0

Views: 2107

Answers (1)

Andreas Mueller
Andreas Mueller

Reputation: 91

You can do that with the provided hamcrest matchers for Iterable:

import org.junit.Test;

import java.util.List;

import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;

public class MatchValuesInAnotherListTest {
    static class Person{
        private final String firstName;
        private final String lastName;

        Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public String getFirstName() {
            return firstName;
        }

        public String getLastName() {
            return lastName;
        }
    }

    @Test
    public void matchLastNames() {
        List<Person> listOfPeople = asList(new Person("Paul", "Smith"), new Person("John", "Davis"));

        assertThat(listOfPeople.stream().map(Person::getLastName).collect(toList()), containsInAnyOrder("Davis", "Smith"));
        assertThat(listOfPeople.stream().map(Person::getLastName).collect(toList()), contains("Smith", "Davis"));
    }
}

They will provide a nicely formatted failure message:

java.lang.AssertionError: 
Expected: iterable over ["Smith"] in any order
     but: Not matched: "Davis"

    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8)

Upvotes: 2

Related Questions