buræquete
buræquete

Reputation: 14678

How to compare members of a list of complex objects with Hamcrest?

Let's say I have a List<A> where

class A {
    private Integer val;
    private String name;
}

and in my test case I get this list, with uncertain size, and content. What I wish to do is to compare val field of two list elements that I know has to be in the list with given name fields;

List<A> list = logic.getList();
assertThat(list, allOf(hasItems(hasProperty("name", equalTo("first")), 
                       hasItems(hasProperty("val", equalTo(***value from another member with name = "second"))));

How can I achieve this, or is this even possible with Hamcrest Matchers?

Upvotes: 3

Views: 2298

Answers (3)

z atef
z atef

Reputation: 7679

Had to do this.

Use:

http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html#containsInAnyOrder(java.util.Collection)

Make sure , class A overrides object equals method, comparing val and name.

class A {
    private Integer val;
    private String name;
}

Upvotes: 0

Kirill
Kirill

Reputation: 8311

You can implement custom Matcher for your needs, e.g. to check that some items with names has same values fields:

final class FooTest {

    static final class Foo {

        final int val;
        final String name;

        // all args constructor
    }

    // custom matcher
    static final class FoosHasSameValues extends TypeSafeMatcher<List<Foo>> {

        private final Set<String> names;

        // all args constructor

        FoosHasSameValues(final String... names) {
            this(new HashSet<>(Arrays.asList(names)));
        }

        @Override
        protected boolean matchesSafely(final List<Foo> items) {
            final List<Integer> values = items.stream()
                // filter only items with specified names
                .filter(i -> this.names.contains(i.name))
                // select only values
                .map(i -> i.val)
                .collect(Collectors.toList());
            if (values.size() != this.names.size()) {
                // matching failed if list doesn't contains all
                // needed items with names
                return false;
            }
            // check https://stackoverflow.com/a/29288616/1723695
            return values.stream().distinct().limit(2).count() <= 1;
        }

        @Override
        public void describeTo(final Description description) {
            description.appendText("has items [")
                .appendValue(String.join(", ", this.names))
                .appendText("] with same values");
        }
    }

    @Test
    void testMatchers() throws Exception {
        MatcherAssert.assertThat(
            Arrays.asList(
                new Foo("first", 1),
                new Foo("second", 1),
                new Foo("third", 2)
            ),
            new FoosHasSameValues("first", "second")
        );
    }
}

Upvotes: 2

tdrury
tdrury

Reputation: 2338

Writing a custom matcher cleans up your test logic:

public class AMatcher extends TypeSafeMatcher<A> {
   A actual;
   public AMatcher(A actual) { this.actual = actual; }

   protected boolean matchesSafely(A a) {
      return a.equals(actual);  // or compare individual fields...
   } 

   public void describeTo(Description d) {
      d.appendText("should match "+actual); // printed out when a match isn't found.
   }
}

then, to use it:

assertThat(list, allOf(new AMatcher(a1), new AMatcher(a2)));

or, if you don't want to create instances of A to create a matcher, create an AMatcher constructor that takes your 'name' and 'val' arguments.

Upvotes: 0

Related Questions