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


103

假设我要使用此签名对方法进行单元测试:

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版本到底是什么?

Answers:


125

谢谢@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"))
));

49

尝试:

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

2
就像是一个侧节点-这是一个棘手的解决方案(不是assertj)
Hartmut P.

46

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

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

(根据Rodrigo Manyari的略微改进进行编辑。它的详细程度略差一些。请参阅评论。)

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


2
稍有改进:assertTrue(myClass.getMyItems()。stream()。anyMatch(item->“ foo” .equals(item.getName()));
Rodrigo Manyari

@RodrigoManyari,右括号缺失
Abdull

1
该解决方案浪费了显示适当错误消息的可能性。
朱利奥·卡钦

@GiulioCaccin我认为不是。如果使用JUnit,则可以/应该使用重载的断言方法并编写assertTrue(...,“我自己的测试失败消息”); 查看更多关于junit.org/junit5/docs/current/api/org/junit/jupiter/api/...
马里奥Eis的

我的意思是,如果您对布尔值进行断言,则会失去自动打印实际/预期差异的能力。可以使用匹配器进行声明,但是您需要修改此响应以使其与此页面中的其他响应类似。
朱利奥·卡钦

20

Assertj擅长于此。

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

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

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


16

AssertJ在extracting()以下方面提供了出色的功能:您可以传递Functions来提取字段。它在编译时提供检查。
您也可以先轻松声明大小。

它会给:

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 可以声明a元素的多个字段,方法是将每个元素的期望值包装到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")
           ); 

4
不知道为什么没有投票。我认为,到目前为止,这是最好的答案。
PeMa '18 -10-6

1
assertJ库比JUnit断言API更易读。
Sangimed

@Sangimed Agreed,我也比较喜欢hamcrest。
davidxxx '18

在我看来,这有点可读性差,因为它将“实际值”与“期望值”分开,并按需要匹配的顺序放置它们。

4

只要您的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")));

假设您实现了一个构造函数,该构造函数接受要声明的值。我意识到这不是一行,但是知道丢失哪个值而不是一次检查两个值很有用。


1
我真的很喜欢您的解决方案,但是他应该修改所有代码进行测试吗?
凯文·鲍沃索

我认为这里的每个答案都需要进行一些测试设置,执行要测试的方法,然后声明属性。从我看到的结果来看,我的答案没有任何实际开销,只是我在seaprate线上有两个断言,因此失败的断言可以清楚地标识出缺少的值。
布拉德(Brad)

最好在assertTrue中也包含一条消息,以使错误消息更易于理解。如果没有消息,则如果失败,JUnit只会抛出一个AssertionFailedError而不会出现任何错误消息。因此最好包含“结果应包含新的MyItem(\“ foo \”)“之类的内容。
最多

是的,你是对的。无论如何,我都会推荐Hamcrest,这些天我从不使用assertTrue()
Brad

附带说明一下,您的POJO或DTO应该定义equals方法
Tayab Hussain

1

AssertJ 3.9.1支持anyMatchmethod中直接谓词的使用。

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

对于任意复杂的情况,这通常是合适的用例。

对于简单条件,我更喜欢使用extractingmethod(请参见上文),因为所得到的被测可迭代项可能支持具有更好可读性的值验证。示例:它可以提供专门的API,例如containsFrank Neblung的答案中的method。或者您也可以anyMatch稍后再调用它,并使用方法参考(例如)"searchedvalue"::equals。也可以将多个提取器放入extracting方法中,然后使用验证结果tuple()

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.