将对象列表与属性列表进行匹配
Match List of objects against list of properties
我正在尝试使用 hamcrest 匹配器将对象列表与其属性的 list/array 进行匹配。对于一个 属性 值,这不是问题,因为我可以这样做:
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 数组中的所有值?
containsInAnyOrder
的一个重载接受一组匹配器作为其参数。因此你可以这样做:
assertThat(
savedGroup.getMembers(),
containsInAnyOrder(
Stream.of(NAMES)
.map(name -> hasProperty("name", is(name)))
.collect(Collectors.toList())
));
(如果使用Java 8,否则需要添加一个循环构建集合)
需要进行一些清理(描述输出),但我认为它确实解决了您的问题:
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 ..
最好的方法 (IMO) 是将重载的 containsInAnyOrder
Matcher 与自定义的 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,它将以 type-safe 的方式从您的对象中提取 属性:
private Matcher<Member> name(String name) {
return new FeatureMatcher<Member, String>(equalTo(name), "name", "name") {
@Override
protected String featureValueOf(Member actual) {
return actual.getName();
}
};
}
这样做的好处是:
- 您将受益于 type-safety 而不是使用
hasProperty
- 您的测试现在描述了您实际想要匹配的内容,即
hasNames
- 生成的代码现在更加灵活和可组合。想匹配单个对象名称?您现在需要做的就是
assertThat(member, has(name("Fred")))
通过移动 equalTo
sub-matcher 成为 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();
}
};
}
我正在尝试使用 hamcrest 匹配器将对象列表与其属性的 list/array 进行匹配。对于一个 属性 值,这不是问题,因为我可以这样做:
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 数组中的所有值?
containsInAnyOrder
的一个重载接受一组匹配器作为其参数。因此你可以这样做:
assertThat(
savedGroup.getMembers(),
containsInAnyOrder(
Stream.of(NAMES)
.map(name -> hasProperty("name", is(name)))
.collect(Collectors.toList())
));
(如果使用Java 8,否则需要添加一个循环构建集合)
需要进行一些清理(描述输出),但我认为它确实解决了您的问题:
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 ..
最好的方法 (IMO) 是将重载的 containsInAnyOrder
Matcher 与自定义的 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,它将以 type-safe 的方式从您的对象中提取 属性:
private Matcher<Member> name(String name) {
return new FeatureMatcher<Member, String>(equalTo(name), "name", "name") {
@Override
protected String featureValueOf(Member actual) {
return actual.getName();
}
};
}
这样做的好处是:
- 您将受益于 type-safety 而不是使用
hasProperty
- 您的测试现在描述了您实际想要匹配的内容,即
hasNames
- 生成的代码现在更加灵活和可组合。想匹配单个对象名称?您现在需要做的就是
assertThat(member, has(name("Fred")))
通过移动 equalTo
sub-matcher 成为 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();
}
};
}