Question

I have different value objects, each with a different set of fields. How can I check against these with a Hamcrest matcher?

    public class ValueObjectA {
        public Integer field1;
        public String field2;
        public long filed3;
        public Object filed4;
    }

    public class ValueObjectB {
        public String field1;
        public int field2;
    }

This is, what I want to do:

    resultA = getResultA();
    ValueObjectA expectedA = new ValueObjectA();
    expectedA.field1 = 4;
    resultB = getResultB();
    ValueObjectB expectedB = new ValueObjectB();
    expectedB.field1 = "foo";
    assertThat(resultA, new ValueObjectMatcher(expectedA));
    assertThat(resultB, new ValueObjectMatcher(expectedB));

I found a PropertyMatcher but that only uses public getters. I can write something similar, that uses reflection to get the public fields. But is there a readymade one?

Was it helpful?

Solution 2

Anyway, I wrote something based on the PropertyMatcher, in case someone wants to write unit tests for eg. com.j256.ormlite :

public class ValueObjectMatcher extends TypeSafeDiagnosingMatcher {
    private final Object expectedVo;
    private final Set<String> fieldNames;
    private final List<FieldMatcher> fieldMatchers;

    public ValueObjectMatcher(final Object expectedVo) {
        Field[] fieldsToMatch = expectedVo.getClass().getFields();
        this.expectedVo = expectedVo;
        this.fieldNames = fieldNamesFrom(fieldsToMatch);
        this.fieldMatchers = fieldMatchersFor(expectedVo, fieldsToMatch);
    }

    @Override
    protected boolean matchesSafely(final Object item, final Description mismatchDescription) {
        return hasAllFields(item, mismatchDescription) && hasMatchingValues(item, mismatchDescription);
    }

    @Override
    public void describeTo(final Description description) {
        description.appendText("same field values as " + expectedVo.getClass().getSimpleName())
                .appendList(" <", ", ", ">", fieldMatchers);
    }

    private boolean hasMatchingValues(final Object item, final Description mismatchDescription) {
        mismatchDescription.appendText(item + " has <");
        int mismatchCount = 0;

        for (FieldMatcher fieldMatcher : fieldMatchers) {
            if (!fieldMatcher.matches(item)) {
                if (mismatchCount != 0) {
                    mismatchDescription.appendText(", ");
                }
                fieldMatcher.describeMismatch(item, mismatchDescription);
                mismatchCount++;
            }
        }

        mismatchDescription.appendText(">");
        return mismatchCount == 0;
    }

    private boolean hasAllFields(final Object item, final Description mismatchDescription) {
        final Field[] fields = item.getClass().getFields();

        final Set<String> itemsFieldNames = fieldNamesFrom(fields);

        boolean result = true;

        for (String fieldName : fieldNames) {
            if (!itemsFieldNames.contains(fieldName)) {
                result = false;
                mismatchDescription.appendText("missing field: " + fieldName);
            }
        }

        return result;
    }

    private List<FieldMatcher> fieldMatchersFor(final Object expectedVo, final Field[] fields) {
        List<FieldMatcher> result = new ArrayList<FieldMatcher>(fields.length);
        try {
            for (Field field : fields) {
                result.add(new FieldMatcher(field, expectedVo));
            }
        }
        catch (NoSuchFieldException e) {
            throw new IllegalStateException("Programmer exception, pls replace programmer: " +
                                            "field list doesn't match with the fields of the provided expectedVo", e);
        }
        catch (IllegalAccessException e) {
            throw new IllegalStateException("Programmer exception, pls replace programmer: " +
                                            "field list doesn't match with the fields of the provided expectedVo", e);
        }
        return result;
    }

    private Set<String> fieldNamesFrom(final Field[] fieldsToMatch) {
        HashSet<String> result = new HashSet<String>();
        for (Field field : fieldsToMatch) {
            result.add(field.getName());
        }
        return result;
    }

    public class FieldMatcher extends DiagnosingMatcher<Object> {

        private final Object expectedFieldValue;
        private final String fieldName;

        private FieldMatcher(Field field, Object expectedVo) throws NoSuchFieldException, IllegalAccessException {
            this.fieldName = field.getName();
            this.expectedFieldValue = expectedVo.getClass().getField(fieldName).get(expectedVo);
        }

        @Override
        protected boolean matches(final Object item, final Description mismatchDescription) {
            try {
                final Field fieldItem = item.getClass().getField(fieldName);
                final Object fieldObjectItem = fieldItem.get(item);

                if (fieldObjectItem == null) {
                    if (expectedFieldValue != null) {
                        mismatchDescription.appendText(fieldName + ": " + fieldObjectItem);
                    }
                } else if (!fieldObjectItem.equals(expectedFieldValue)) {
                    mismatchDescription.appendText(fieldName + ": " + fieldObjectItem);
                }
            }
            catch (IllegalAccessException e) {
                mismatchDescription.appendText(fieldName + " is inaccessible");
                e.printStackTrace();
            }
            catch (NoSuchFieldException e) {
                mismatchDescription.appendText(fieldName + " doesn't exist");
                e.printStackTrace();
            }

            return false;
        }

        @Override
        public void describeTo(final Description description) {
            description.appendText(fieldName + ": " + expectedFieldValue);
        }
    }
}

OTHER TIPS

You can use a library we recent open sourced for doing this. It works similar to the PropertyMatcher but was designed to do deep object graphs. An upshot is that it "just works" on public fields.

shazamcrest on github

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top