Is there a way to do deep comparison on a nested property with Hamcrest

11,315

Solution 1

You can nest hasProperty calls:

assertThat(results, hasItem(hasProperty("id", hasProperty("fooID1", equalTo("FOOID1")))));

For deeper nestings this might be a bit unwieldy.

Solution 2

I've achieved the result you expected with this simple utility method:

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;
}

which I am able to use in asserts like this:

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

check if this is of any help

Solution 3

I did not find a API solution to your problem, but found on source of 1.3 hamcrest that the HasPropertyWithValue matcher really does not dive into nested properties.

I've made a lousy solution (please observe that the messages when not found are not working properly):

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.hamcrest.beans.PropertyUtil;

public class NestedPropertyMatcher<T> extends TypeSafeDiagnosingMatcher<T>{

    private final String[] props;
    private final String path;
    private final Matcher<?> valueMatcher;

    @Override
    public boolean matchesSafely(T bean, Description mismatch) {
        if (props.length == 1) {
            return org.hamcrest.beans.HasPropertyWithValue.hasProperty(props[props.length - 1], valueMatcher).matches(bean);
        } else {
            Object aux = bean;
            for (int i = 0; i < props.length - 1; i++) {
                if (!org.hamcrest.beans.HasProperty.hasProperty(props[i]).matches(aux)) {
                    return false;
                } else {
                    PropertyDescriptor pd = PropertyUtil.getPropertyDescriptor(props[i], aux);
                    try {
                        aux = pd.getReadMethod().invoke(aux);
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                        mismatch.appendText("Exception while trying to access property value: " + e.getLocalizedMessage());
                        return false;
                    }
                }
            }
            return org.hamcrest.beans.HasPropertyWithValue.hasProperty(props[props.length - 1], valueMatcher).matches(aux);
        }
    }

    private NestedPropertyMatcher(String path, String[] propertiesTokens, Matcher<?> valueMatcher) {
        this.path = path;
        this.props = propertiesTokens;
        this.valueMatcher = valueMatcher;
    }

    public static <T> Matcher<T> hasPathProperty(String propertyPath, Matcher<?> valueMatcher) {
        String[] props = propertyPath.split("\\.");
        return new NestedPropertyMatcher<T>(propertyPath, props, valueMatcher);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("hasProperty(").appendValue(path).appendText(", ").appendDescriptionOf(valueMatcher).appendText(") did not found property");
    }
}

Pretty sure that the hamcrest folks will make a better job than mine, but I think this code will be enough for you.

Share:
11,315
Gaurav Rawat
Author by

Gaurav Rawat

searching for answers to unanswered questions :) Human,Coder,Poet and Photographer in that order

Updated on July 31, 2022

Comments

  • Gaurav Rawat
    Gaurav Rawat almost 2 years

    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)
    

    any help or workaround on this issue .

  • Paulo Araújo
    Paulo Araújo about 8 years
    Changed the matcher function name to avoid collisions.
  • Gaurav Rawat
    Gaurav Rawat about 8 years
    Thanks that works like a charm but true unwieldy when it comes to a deeper hierarchy ..
  • Loathian
    Loathian over 6 years
    This is great, just what I needed, thanks for sharing.