1. 概述

在Java编写单元测试时(特别是使用JUnit框架),我们经常需要验证列表中的元素是否具有特定属性。

Hamcrest作为广泛使用的Matcher库,提供了直观且富有表现力的方式来执行这些检查。

本文将探讨如何使用JUnit和Hamcrest的Matcher来检查列表中是否包含具有特定属性的元素。

2. 环境准备与示例

首先在pom.xml中添加Hamcrest依赖:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest</artifactId>
    <version>2.2</version>
    <scope>test</scope>
</dependency>

可在Maven中央仓库查看最新版本。

创建一个简单的POJO类:

public class Developer {
    private String name;
    private int age;
    private String os;
    private List<String> languages;
 
    public Developer(String name, int age, String os, List<String> languages) {
        this.name = name;
        this.age = age;
        this.os = os;
        this.languages = languages;
    }
    // ... getters省略
}

接下来创建测试数据列表:

private static final List<Developer> DEVELOPERS = List.of(
    new Developer("Kai", 28, "Linux", List.of("Kotlin", "Python")),
    new Developer("Liam", 26, "MacOS", List.of("Java", "C#")),
    new Developer("Kevin", 24, "MacOS", List.of("Python", "Go")),
    new Developer("Saajan", 22, "MacOS", List.of("Ruby", "Php", "Typescript")),
    new Developer("Eric", 27, "Linux", List.of("Java", "C"))
);

我们将使用DEVELOPERS列表演示如何检查元素属性。

3. 使用hasItem()hasProperty()

Hamcrest提供了丰富的Matcher组合。我们可以将hasProperty()hasItem()结合使用:

assertThat(DEVELOPERS, hasItem(hasProperty("os", equalTo("Linux"))));

✅ 这个示例检查列表中是否存在os属性为"Linux"的元素

通过修改属性名可验证其他属性:

assertThat(DEVELOPERS, hasItem(hasProperty("name", is("Kai"))));

is()equalTo()的别名,用于检查name为"Kai"的元素。

除了equalTo()is()我们还能在hasProperty()中使用其他Matcher

assertThat(DEVELOPERS, hasItem(hasProperty("age", lessThan(28))));
assertThat(DEVELOPERS, hasItem(hasProperty("languages", hasItem("Go"))));

断言语句读起来像自然语言:"断言DEVELOPERS列表包含一个元素,该元素的age属性小于28"。

4. anyOf()allOf()组合Matcher

hasProperty()适合检查单个属性。通过anyOf()allOf()组合多个hasProperty(),可验证元素是否满足多条件

anyOf()满足任一Matcher即通过

assertThat(DEVELOPERS, hasItem(
  anyOf(
      hasProperty("languages", hasItem("C")),
      hasProperty("os", is("Windows"))) // <-- 没有开发者用Windows
));

⚠️ 虽然没有元素的os是"Windows",但存在元素("Eric")的languages包含"C",断言仍通过。

➡️ anyOf()对应OR逻辑:元素满足languages包含"C" os是"Windows"。

allOf()对应AND逻辑

assertThat(DEVELOPERS, hasItem(
  allOf(
      hasProperty("languages", hasItem("C")),
      hasProperty("os", is("Linux")),
      hasProperty("age", greaterThan(25)))
));

✅ 检查是否存在同时满足三个条件的元素。"Eric"满足所有条件,测试通过。

若修改为不存在的组合:

assertThat(DEVELOPERS, not(hasItem( // <-- not()取反
  allOf(
      hasProperty("languages", hasItem("C#")),
      hasProperty("os", is("Linux")),
      hasProperty("age", greaterThan(25)))
)));

❌ 没有元素同时满足三个条件,测试通过(因not()取反)。

5. 使用JUnit的assertTrue()Stream.anyMatch()

Hamcrest提供了便捷方式,我们也可以用标准JUnit的assertTrue()结合Java Stream API的anyMatch()实现相同检查

**anyMatch()在流中任一元素满足条件时返回true**:

assertTrue(DEVELOPERS.stream().anyMatch(dev -> dev.getOs().equals("Linux")));
assertTrue(DEVELOPERS.stream().anyMatch(dev -> dev.getAge() < 28));
assertTrue(DEVELOPERS.stream().anyMatch(dev -> dev.getLanguages().contains("Go")));

使用lambda时可直接调用getter方法,比hasProperty()的字符串属性名更直观。

复杂条件检查也很简单:

assertTrue(DEVELOPERS.stream().anyMatch(dev -> 
    dev.getLanguages().contains("C") && dev.getOs().equals("Linux")
));

✅ 通过lambda表达式同时检查多个属性。

6. 总结

本文探讨了使用JUnit和Hamcrest验证列表元素属性的多种方式:

Hamcrest优势

  • 提供丰富的Matcher组合
  • 测试代码可读性强
  • 支持复杂条件组合(anyOf/allOf

Stream API替代方案

  • 使用assertTrue() + anyMatch()
  • 更灵活的lambda表达式
  • 避免字符串属性名硬编码

完整示例代码见GitHub仓库


原始标题:Check if a List Contains Elements With Certain Properties in Hamcrest | Baeldung