JavaHamcrest icon indicating copy to clipboard operation
JavaHamcrest copied to clipboard

Make hamcrest hasProperty to match on nested properties

Open gauravbrills opened this issue 8 years ago • 9 comments

Use case below

http://stackoverflow.com/questions/36363372/is-there-a-way-to-do-deep-comparison-on-a-nested-property-with-hamcrest/36364718#36364718

I use hamcrest for most of my testing ,but have encountered a issue with it not being able to test a property one level down in the object graph .A snipped of my test case is below

final List<Foo> foos= fooRepository.findAll(spec);
      assertThat(results, is(notNullValue()));
      assertThat(results, hasItem(hasProperty("id.fooID1", equalTo("FOOID1"))));

so here I want to check if in the list of foos I have a property id.fooID1 equla to FOOID1 .Here I am going one level down to check my nested property .This doesnt currently work in hamcrest and I get the following error.


java.lang.AssertionError: 
Expected: a collection containing hasProperty("id.fooID1", "FOOID1")
     but: No property "id.fooID1"
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.junit.Assert.assertThat(Assert.java:956)
    at org.junit.Assert.assertThat(Assert.java:923)

THink hamcrest should enhance for convenient nested property comparisons.

gauravbrills avatar Apr 01 '16 19:04 gauravbrills

I use the following method to make assertions on graphs of properties.

private static <T> Matcher<T> hasGraph(String graphPath, Matcher<T> matcher) {

    List<String> properties = Arrays.asList(graphPath.split("\\."));
    ListIterator<String> iterator =
        properties.listIterator(properties.size());

    Matcher<T> ret = matcher;
    while (iterator.hasPrevious()) {
        ret = hasProperty(iterator.previous(), ret);
    }
    return ret;
}

It simply creates nested matchers but it is easy to read as it can be used with a syntax like this:

assertThat(bean, hasGraph("beanProperty.subProperty.subSubProperty", notNullValue()));

It would be nice to have this functionality on the org.hamcrest.Matchers class.

cristcost avatar May 12 '16 11:05 cristcost

+1, unfortunately, seems like there isn't much interest for this feature :(

priiduneemre avatar Oct 03 '16 08:10 priiduneemre

Not sure what to do, to support this request. I use the workaround of cristcost in some projects but would love to see that in Matchers.

mscinar avatar Mar 02 '17 14:03 mscinar

Hm, great issue. My usecase - matching arbitrary properties in Spring Integration message payloads.

mikegolod avatar Feb 16 '18 14:02 mikegolod

Good point

sf105 avatar Feb 18 '18 20:02 sf105

One small correction, you don't need to do the null value check. The matcher should handle that.

sf105 avatar Feb 18 '18 20:02 sf105

I've pushed HasPropertyWithValue.hasPropertyAtPath() based on your solution. See what you think.

sf105 avatar Feb 18 '18 22:02 sf105

I've pushed HasPropertyWithValue.hasPropertyAtPath() based on your solution. See what you think.

Due to the signature of this method, it can currently only be used if every bean on the path is of the same type T. Similar to

public static <T> Matcher<T> hasProperty(String propertyName, Matcher<?> valueMatcher)

it should rather look like this (or use the iterator, if you insist):

@SuppressWarnings("unchecked")
public static <T> Matcher<T> hasPropertyPath(String path, Matcher<?> valueMatcher) {
    Matcher<?> ret = valueMatcher;

    String[] properties = path.split("\\.");
    for (int i = properties.length - 1; i >= 0; --i) {
        ret = new HasPropertyWithValue<>(properties[i], ret, "%s.");
    }

    return (Matcher<T>) ret;
}

This change would allow this:

A a = ...
Matcher<D> dMatcher = ...
assertThat(a, hasPropertyPath("b.c.d", dMatcher));

anma avatar Nov 29 '19 17:11 anma

I agree with @anma, the matcher should have exactly the same signature as hasProperty. What's the reason for hasPropertyAtPath to don't use Matcher<?>? Any possibility to change the signature @sf105 ?

lujop avatar Aug 23 '21 07:08 lujop