如何使用Hamcrest比较列表时忽略元素的指定属性
Hamcrest 对比列表忽略指定字段的实现方案
原生 Hamcrest 没有直接提供ignoreFields链式方法,你可以通过以下两种方案实现需求:
方案一:自定义元素匹配器+containsInAnyOrder组合实现
Java 8+ 搭配 Hamcrest 2.2及以上版本可以直接用流生成匹配器集合,代码示例如下:
import org.hamcrest.TypeSafeMatcher; import org.hamcrest.Description; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; // 假设你的列表元素类型是UserBean List<Matcher<? super UserBean>> matchers = anotherList.stream() .map(expectedItem -> new TypeSafeMatcher<UserBean>() { @Override protected boolean matchesSafely(UserBean actualItem) { // 对比除accountNumber之外的所有业务字段即可 return Objects.equals(expectedItem.getUsername(), actualItem.getUsername()) && Objects.equals(expectedItem.getPhone(), actualItem.getPhone()) && Objects.equals(expectedItem.getCreateTime(), actualItem.getCreateTime()); } @Override public void describeTo(Description description) { description.appendText("UserBean without accountNumber: " + expectedItem); } }) .collect(Collectors.toList()); // 执行断言 assertThat(list, containsInAnyOrder(matchers));
如果你的Hamcrest版本低于2.2,需要额外引入hamcrest-library依赖才能使用containsInAnyOrder的集合参数重载
方案二:封装通用忽略字段匹配器(可复用)
如果需要频繁使用忽略字段的列表对比,可以封装通用工具类,实现你想要的简化调用效果:
public class HamcrestListMatcher { public static <T> org.hamcrest.Matcher<Iterable<? extends T>> containsInAnyOrderIgnoreFields( Collection<T> expectedList, String... ignoreFields) { return new TypeSafeMatcher<Iterable<? extends T>>() { @Override protected boolean matchesSafely(Iterable<? extends T> actualIterable) { List<T> actualList = new ArrayList<>(); actualIterable.forEach(actualList::add); // 先校验长度一致 if (actualList.size() != expectedList.size()) { return false; } Set<String> ignoreFieldSet = new HashSet<>(Arrays.asList(ignoreFields)); // 逐一匹配元素,忽略指定字段 for (T expected : expectedList) { boolean matchFound = actualList.stream() .anyMatch(actual -> beanEqualsIgnoreFields(expected, actual, ignoreFieldSet)); if (!matchFound) { return false; } // 移除已匹配的元素,避免重复匹配 actualList.removeIf(actual -> beanEqualsIgnoreFields(expected, actual, ignoreFieldSet)); } return actualList.isEmpty(); } @Override public void describeTo(Description description) { description.appendText("list contains elements in any order, ignore fields: ") .appendValue(Arrays.toString(ignoreFields)); } // 反射对比两个Bean除指定字段外的所有属性 private boolean beanEqualsIgnoreFields(T o1, T o2, Set<String> ignoreFieldSet) { if (o1 == o2) return true; if (o1 == null || o2 == null || o1.getClass() != o2.getClass()) return false; // 遍历所有声明字段对比 for (Field field : o1.getClass().getDeclaredFields()) { if (ignoreFieldSet.contains(field.getName())) { continue; } field.setAccessible(true); try { if (!Objects.equals(field.get(o1), field.get(o2))) { return false; } } catch (IllegalAccessException e) { throw new RuntimeException("字段对比失败", e); } } return true; } }; } }
封装完成后就可以直接用你期望的调用方式:
assertThat(list, HamcrestListMatcher.containsInAnyOrderIgnoreFields(anotherList, "accountNumber"));
小提示:如果项目已经引入了AssertJ、Mockito等工具库,也可以直接使用对应库提供的忽略字段对比能力,搭配Hamcrest使用无需自己实现反射逻辑。
内容的提问来源于stack exchange,提问作者dubnic
相关产品推荐
相关产品推荐

