根据属性列表匹配对象列表

时间:2016-01-26 09:20:57

标签: java junit matcher hamcrest

我正在尝试使用hamcrest匹配器将对象列表与其属性的列表/数组进行匹配。对于一个属性值,这不是问题,因为我可以这样做:

assertThat(savedGroup.getMembers(),
    containsInAnyOrder(hasProperty("name", is(NAMES[0]))));

对于多个属性值,我可以使用多个hasProperty()调用

assertThat(savedGroup.getMembers(),
    containsInAnyOrder(
        hasProperty("name", is(NAMES[0])),
        hasProperty("name", is(NAMES[1]))));

但是有没有一种通用的方法来匹配NAMES数组中的所有值?

3 个答案:

答案 0 :(得分:3)

执行此操作的最佳方式(IMO)是将重载的containsInAnyOrder匹配器与自定义FeatureMatcher结合使用。最终你的代码看起来像这样:

String[] expectedNames = new String[] { "John", "Bob", "Carol"};
assertThat(savedGroup.getMembers(), hasNames(expectedNames));

hasNames的实施方式如下:

private Matcher<Iterable<? extends Member>> hasNames(String[] expectedNames) {
    return containsInAnyOrder(Arrays.stream(expectedNames).map(name -> name(name)).collect(Collectors.toList()));
}

最后一部分是对name的调用,它生成一个Matcher,它将以类型安全的方式从对象中提取属性:

private Matcher<Member> name(String name) {
    return new FeatureMatcher<Member, String>(equalTo(name), "name", "name") {
        @Override
        protected String featureValueOf(Member actual) {
            return actual.getName();
        }
    };
}

这样做的好处是:

  • 您可以获得类型安全的好处,而不是使用hasProperty
  • 您的测试现在描述了您实际想要匹配的内容,即hasNames
  • 现在生成的代码更灵活,更易于组合。想要匹配单个对象名称?您现在需要做的只是assertThat(member, has(name("Fred")))

通过将equalTo子匹配器移动到hasNames调用中,您可以获得更多可组合性:

private Matcher<Iterable<? extends Member>> hasNames(String[] expectedNames) {
    return containsInAnyOrder(Arrays.stream(expectedNames).map(name -> name(equalTo(name))).collect(Collectors.toList()));
}

private Matcher<Member> name(Matcher<String> nameMatcher) {
    return new FeatureMatcher<Member, String>(nameMatcher, "name", "name") {
        @Override
        protected String featureValueOf(Member actual) {
            return actual.getName();
        }
    };
}

答案 1 :(得分:1)

containsInAnyOrder的一个重载接受一组匹配器作为其参数。因此你可以这样做:

assertThat(
    savedGroup.getMembers(),
    containsInAnyOrder(
        Stream.of(NAMES)
              .map(name -> hasProperty("name", is(name)))
              .collect(Collectors.toList()) 
    ));

(如果使用Java 8,否则需要添加一个构建集合的循环)

答案 2 :(得分:0)

需要进行一些清理(描述输出),但我认为它确实可以解决您的问题:

package org.example.matchers;

import java.util.List;

import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.TypeSafeMatcher;

public class ContainsArrayElementsInAnyOrder<T> extends TypeSafeMatcher<List<T>> {

    private T[] toMatch;

    public ContainsArrayElementsInAnyOrder(final T[] toMatch) {
        this.toMatch = toMatch;
    }

    @Override
    protected boolean matchesSafely(List<T> item) {
        if(item.size() != toMatch.length) {
            return false;
        }
        for (T t : toMatch) {
            if(!item.contains(t)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void describeMismatchSafely(List<T> item, Description mismatchDescription) {
        mismatchDescription.appendValueList("[", ",", "]", item);
    }

    @Override
    public void describeTo(Description description) {
        description.appendValueList("[", ",", "]", toMatch);
    }

    @Factory
    public static <T> ContainsArrayElementsInAnyOrder<T> containsArrayElementsInAnyOrder(T[] elements) {
        return new ContainsArrayElementsInAnyOrder<T>(elements);
    }    


}

测试:

@Test
public void shouldContainsInAnyOrderSameElementsInArrayAsInList() {
    final String[] NAME = new String[]{"name3", "name1", "name2"};

    final List<String> result = new ArrayList<>(3);
    result.add("name2");
    result.add("name1");
    result.add("name4");

    assertThat(result, containsArrayElementsInAnyOrder(NAME));
}

如果不匹配则输出:

java.lang.AssertionError: 
Expected: ["name3","name1","name2"]
     but: ["name2","name1","name4"]
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.junit.Assert.assertThat(Assert.java:956)
    at org.junit.Assert.assertThat(Assert.java:923)
    at ..