或任何 Iterable 是否包含两个 MyItem 实例,它们的 "name" 属性具有值 "foo" 和 "bar"。如果任何其他属性不匹配,我真的不关心这个测试的目的。如果名称匹配,则测试......" /> 或任何 Iterable 是否包含两个 MyItem 实例,它们的 "name" 属性具有值 "foo" 和 "bar"。如果任何其他属性不匹配,我真的不关心这个测试的目的。如果名称匹配,则测试......"> 或任何 Iterable 是否包含两个 MyItem 实例,它们的 "name" 属性具有值 "foo" 和 "bar"。如果任何其他属性不匹配,我真的不关心这个测试的目的。如果名称匹配,则测试......" />
ChatGPT解决这个技术问题 Extra ChatGPT

如何断言 Iterable 包含具有特定属性的元素?

假设我想用这个签名对一个方法进行单元测试:

List<MyItem> getMyItems();

假设 MyItem 是一个具有许多属性的 Pojo,其中之一是 "name",通过 getName() 访问。

我关心的是验证 List<MyItem> 或任何 Iterable 是否包含两个 MyItem 实例,它们的 "name" 属性具有值 "foo""bar"。如果任何其他属性不匹配,我真的不关心这个测试的目的。如果名称匹配,则测试成功。

如果可能的话,我希望它是单行的。这是我想做的那种事情的一些“伪语法”。

assert(listEntriesMatchInAnyOrder(myClass.getMyItems(), property("name"), new String[]{"foo", "bar"});

Hamcrest 对这类事情有好处吗?如果是这样,我上面的伪语法的 hamcrest 版本到底是什么?


P
PiroXXI

谢谢@Razvan,他为我指明了正确的方向。我能够在一行中获得它,并且我成功地为 Hamcrest 1.3 找到了进口。

进口:

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;

编码:

assertThat( myClass.getMyItems(), contains(
    hasProperty("name", is("foo")), 
    hasProperty("name", is("bar"))
));

如果您不知道项目的顺序,请改用 containsInAnyOrder(来自同一父类):)
d
davidxxx

AssertJ 在 extracting() 中提供了一个出色的功能:您可以传递 Function 来提取字段。它在编译时提供检查。
您也可以轻松地先断言大小。

它会给:

import static org.assertj.core.api.Assertions;

Assertions.assertThat(myClass.getMyItems())
          .hasSize(2)
          .extracting(MyItem::getName)
          .containsExactlyInAnyOrder("foo", "bar"); 

containsExactlyInAnyOrder() 断言列表仅包含这些值,无论顺序如何。

要断言列表包含这些值,无论顺序如何,但也可能包含其他值,请使用 contains()

.contains("foo", "bar"); 

附带说明:要从 List 的元素断言多个字段,使用 AssertJ,我们通过将每个元素的预期值包装到 tuple() 函数中来做到这一点:

import static org.assertj.core.api.Assertions;
import static org.assertj.core.groups.Tuple;

Assertions.assertThat(myClass.getMyItems())
          .hasSize(2)
          .extracting(MyItem::getName, MyItem::getOtherValue)
          .containsExactlyInAnyOrder(
               tuple("foo", "OtherValueFoo"),
               tuple("bar", "OtherValueBar")
           ); 

不明白为什么这没有赞成票。我认为,这是迄今为止最好的答案。
assertJ 库比 JUnit 断言 API 更具可读性。
@Sangimed 同意,而且我更喜欢它而不是 hamcrest。
在我看来,这有点不太可读,因为它将“实际值”与“预期值”分开,并将它们按需要匹配的顺序排列。
M
Mario Eis

它不是特别是 Hamcrest,但我认为这里值得一提。我在 Java8 中经常使用的是:

assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName())));

(编辑为 Rodrigo Manyari 的轻微改进。它不那么冗长。见评论。)

它可能有点难以阅读,但我喜欢这种类型和重构安全性。组合测试多个 bean 属性也很酷。例如,在过滤器 lambda 中使用类似 java 的 && 表达式。


轻微改进: assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName()));
@RodrigoManyari,缺少右括号
此解决方案浪费了显示适当错误消息的可能性。
@GiulioCaccin 我不这么认为。如果您使用 JUnit,您可以/应该使用重载的断言方法并编写 assertTrue(..., "My own test failure message");在 junit.org/junit5/docs/current/api/org/junit/jupiter/api/… 上查看更多信息
我的意思是,如果您对布尔值进行断言,您将失去自动打印实际/预期差异的能力。可以使用匹配器进行断言,但您需要将此响应修改为与此页面中的其他响应类似才能执行此操作。
a
acdcjunior

尝试:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));

就像一个侧节点 - 这是一个hamcrest解决方案(不是assertj)
F
Frank Neblung

Assertj 擅长这一点。

import static org.assertj.core.api.Assertions.assertThat;

    assertThat(myClass.getMyItems()).extracting("name").contains("foo", "bar");

与 hamcrest 相比,assertj 的一大优点是易于使用代码完成。


弗兰克的另一种方法: assertThat(list) .containsAll(Arrays.asList(id1,id2));
B
Brad

只要您的 List 是一个具体的类,只要您在 MyItem 上实现了 equals() 方法,就可以简单地调用 contains() 方法。

// given 
// some input ... you to complete

// when
List<MyItems> results = service.getMyItems();

// then
assertTrue(results.contains(new MyItem("foo")));
assertTrue(results.contains(new MyItem("bar")));

假设您已经实现了一个构造函数,该构造函数接受您要断言的值。我意识到这不是在单行上,但是知道缺少哪个值而不是同时检查两个值很有用。


我真的很喜欢你的解决方案,但他应该修改所有代码进行测试吗?
我认为这里的每个答案都需要一些测试设置,执行测试方法,然后断言属性。从我所看到的来看,我的答案没有真正的开销,只是我在单独的行上有两个断言,因此失败的断言可以清楚地确定缺少什么值。
最好在 assertTrue 中也包含一条消息,以便更容易理解错误消息。如果没有消息,如果它失败,JUnit 只会抛出一个 AssertionFailedError 而没有任何错误消息。所以最好包括“结果应该包含新的 MyItem(\"foo\")”之类的内容。
是的你是对的。无论如何我都会推荐 Hamcrest,而且这些天我从不使用 assertTrue()
附带说明一下,您的 POJO 或 DTO 应该定义 equals 方法
T
Tomáš Záluský

AssertJ 3.9.1 支持在 anyMatch 方法中直接使用谓词。

assertThat(collection).anyMatch(element -> element.someProperty.satisfiesSomeCondition())

这通常适用于任意复杂条件的用例。

对于简单的条件,我更喜欢使用 extracting 方法(见上文),因为生成的 iterable-under-test 可能支持具有更好可读性的值验证。示例:它可以提供专门的 API,例如 Frank Neblung 回答中的 contains 方法。或者您可以稍后调用 anyMatch 并使用方法引用,例如 "searchedvalue"::equals。也可以将多个提取器放入 extracting 方法中,随后使用 tuple() 验证结果。


s
seregamorph

除了 hasProperty,您可以尝试使用具有提取功能的 hamcrest-more-matchers where 匹配器。在你的情况下,它看起来像:

import static com.github.seregamorph.hamcrest.MoreMatchers.where;

assertThat(myClass.getMyItems(), contains(
    where(MyItem::getName, is("foo")), 
    where(MyItem::getName, is("bar"))
));

这种方法的优点是:

如果值是在 get 方法中计算的,并不总是可以按字段验证

在不匹配的情况下,应该有一个带有诊断的失败消息(注意已解决的方法参考 MyItem.getName:

Expected: iterable containing [Object that matches is "foo" after call
MyItem.getName, Object that matches is "bar" after call MyItem.getName]
     but: item 0: was "wrong-name"

它适用于 Java 8、Java 11 和 Java 14


W
WesternGun

使用 Stream,您还可以:

List<String> actual = myList.stream().map(MyClass::getName).collect(toList());
assertThat(actual, hasItem("expectedString1"));

因为使用 anyMatch()allMatch(),您知道列表中的 一些 值在列表中,但是您的实际列表可能只包含 5 个值,而在 anyMatch() 中您有 6 个;您不知道是否存在 所有 值。使用 hasItem(),您确实可以检查您想要的每个值。