如何断言具有 Java 记录的特性?

我在测试中有一段代码,使用Hamcrest 2.2检查结果列表是否包含某些属性:

assertThat(result.getUsers(), hasItem(
    hasProperty("name", equalTo(user1.getName()))
));
assertThat(result.getUsers(), hasItem(
    hasProperty("name", equalTo(user2.getName()))
));

这在正常上课时工作得很好。但是在我将其更改为a之后,Hamcrest抱怨没有财产命名:NameDtoRecordhasPropertyname

java.lang.AssertionError:
Expected: a collection containing hasProperty("name", "Test Name")
     but: mismatches were: [No property "name", No property "name"]

是否有其他匹配器可用于实现与以前相同的匹配?或者我可以使用其他一些解决方法来使其与记录一起使用?


答案 1

记录字段的访问器方法不遵循常规的 JavaBeans 约定,因此记录(例如 )将具有一个访问器方法,其名称不是 。Userpublic record User (String name) {}name()getName()

我怀疑这就是为什么Hamcrest认为没有财产的原因。我不认为在Hamcrest中除了编写自定义匹配器之外,没有其他开箱即用的方法。

这是一个受现有HasPropertyWithValue启发的自定义。这里使用的主要实用程序是Java的Class.getRecordComponents()HasRecordComponentWithValue

public static class HasRecordComponentWithValue<T> extends TypeSafeDiagnosingMatcher<T> {
    private static final Condition.Step<RecordComponent,Method> WITH_READ_METHOD = withReadMethod();
    private final String componentName;
    private final Matcher<Object> valueMatcher;

    public HasRecordComponentWithValue(String componentName, Matcher<?> valueMatcher) {
        this.componentName = componentName;
        this.valueMatcher = nastyGenericsWorkaround(valueMatcher);
    }

    @Override
    public boolean matchesSafely(T bean, Description mismatch) {
        return recordComponentOn(bean, mismatch)
                  .and(WITH_READ_METHOD)
                  .and(withPropertyValue(bean))
                  .matching(valueMatcher, "record component'" + componentName + "' ");
    }

    private Condition.Step<Method, Object> withPropertyValue(final T bean) {
        return new Condition.Step<Method, Object>() {
            @Override
            public Condition<Object> apply(Method readMethod, Description mismatch) {
                try {
                    return matched(readMethod.invoke(bean, NO_ARGUMENTS), mismatch);
                } catch (Exception e) {
                    mismatch.appendText(e.getMessage());
                    return notMatched();
                }
            }
        };
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("hasRecordComponent(").appendValue(componentName).appendText(", ")
                   .appendDescriptionOf(valueMatcher).appendText(")");
    }

    private Condition<RecordComponent> recordComponentOn(T bean, Description mismatch) {
        RecordComponent[] recordComponents = bean.getClass().getRecordComponents();
        for(RecordComponent comp : recordComponents) {
            if(comp.getName().equals(componentName)) {
                return matched(comp, mismatch);
            }
        }
        mismatch.appendText("No record component \"" + componentName + "\"");
        return notMatched();
    }


    @SuppressWarnings("unchecked")
    private static Matcher<Object> nastyGenericsWorkaround(Matcher<?> valueMatcher) {
        return (Matcher<Object>) valueMatcher;
    }

    private static Condition.Step<RecordComponent,Method> withReadMethod() {
        return new Condition.Step<RecordComponent, java.lang.reflect.Method>() {
            @Override
            public Condition<Method> apply(RecordComponent property, Description mismatch) {
                final Method readMethod = property.getAccessor();
                if (null == readMethod) {
                    mismatch.appendText("record component \"" + property.getName() + "\" is not readable");
                    return notMatched();
                }
                return matched(readMethod, mismatch);
            }
        };
    }

    @Factory
    public static <T> Matcher<T> hasRecordComponent(String componentName, Matcher<?> valueMatcher) {
        return new HasRecordComponentWithValue<T>(componentName, valueMatcher);
    }
}

答案 2

我发现只有使用AssertJ就可以实现相同的测试,至少在这种情况下是这样:

assertThat(result.getUsers())
        .extracting(UserDto::name)
        .contains(user1.getName(), user2.getName());

它没有使用,所以它并不能完全解决问题。hasProperty


推荐