134

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

List<MyItem> getMyItems();

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

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

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

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

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

4

9 回答 9

149

谢谢@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"))
));
于 2012-08-28T21:28:18.883 回答
55

尝试:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
于 2012-08-28T19:45:33.317 回答
54

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

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

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

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

于 2015-10-19T09:50:43.110 回答
47

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

附带说明:要从 a 的元素断言多个字段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")
           ); 
于 2018-08-12T19:33:55.300 回答
30

Assertj擅长这一点。

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

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

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

于 2015-12-22T13:39:37.507 回答
7

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

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

于 2012-08-28T19:51:33.950 回答
2

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

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

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

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

于 2019-11-08T12:29:35.360 回答
0

或者,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
于 2020-10-09T08:59:28.373 回答
0

使用 Stream,您还可以:

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

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

于 2021-09-15T13:06:07.020 回答