有没有办法用 Hamcrest 对嵌套 属性 进行深入比较

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

我的大部分测试都使用 hamcrest,但遇到了一个问题,它无法测试对象图中的 属性 下一级。下面是我的测试用例的片段

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

所以在这里我想检查 foos 列表中是否有一个 属性 id.fooID1 equla 到 FOOID1 。在这里我要向下一级检查我的嵌套 属性 。这目前不能在 hamcrest 中工作,我收到以下错误。

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)

有关此问题的任何帮助或解决方法。

我没有找到 API 解决您问题的方法,但在 1.3 hamcrest 的源代码中发现 HasPropertyWithValue 匹配器确实没有深入到嵌套属性中。

我做了一个糟糕的解决方案(请注意未找到的消息无法正常工作):

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

很确定 hamcrest 的人会比我的做得更好,但我认为这段代码对你来说已经足够了。

您可以嵌套 hasProperty 个调用:

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

对于更深的嵌套,这可能有点笨拙。

我已经通过这个简单的实用方法达到了您预期的结果:

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

我可以在这样的断言中使用它:

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

检查这是否有帮助

您可以尝试使用Hamcrest BeanMatcher APT generator。它可以为您的 Foo bean(以及所有嵌套 bean)生成 Matcher,因此您将能够使用以下构造

assertThat(results, hasItem(fooMatcher()
     .withId(idBeanMatcher()
       .withFooID1("FOOID1"))));

对于这种情况,它看起来有点矫枉过正,但如果您需要检查更复杂的情况,它可能会大大简化代码。

此外,它会在每次更新时自动重新生成 BeanMatchers,因此如果您更改某些 属性 的名称(例如 FooID1FooId1) 比一些硬编码字符串更容易检测和修复。