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

问题描述 投票:0回答:10

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

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

java unit-testing junit4 hamcrest
10个回答
168
投票

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

121
投票

AssertJ 在

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

它会给:

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

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.assertThat;
import static org.assertj.core.groups.Tuple.tuple;

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

66
投票

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

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

(编辑 Rodrigo Manyari 的轻微改进。稍微不那么冗长。请参阅评论。)

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


58
投票

尝试:

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

31
投票

Assertj 擅长此。

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

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

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


9
投票

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

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


5
投票

AssertJ 3.9.1 支持在

anyMatch
方法中直接使用谓词。

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

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

对于简单的条件,我更喜欢使用

extracting
方法(见上文),因为结果可迭代的测试可能支持具有更好可读性的值验证。 例如:它可以提供专门的API,例如Frank Neblung的答案中的
contains
方法。或者您可以稍后调用
anyMatch
并使用方法引用,例如
"searchedvalue"::equals
。也可以将多个提取器放入
extracting
方法中,随后使用
tuple()
验证结果。


1
投票

使用 Stream,您还可以执行以下操作:

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

因为使用

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


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-method 中计算的,并不总是可以通过字段验证
  • 如果不匹配,应该会出现一条带有诊断的失败消息(请注意已解析的方法参考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

0
投票

使用 org.assertj.core.api.AbstractAssert#asList() 的解决方案

有时我们需要断言一个包含

List
类型属性的对象。我的意思是在
assertThat
或任何其他类似方法中显式断言属性本身不简洁的情况(例如,在
assertThatExceptionOfType
的情况下)。

解决方案如下:

assertThat(myClass)
    .extracting(MyClass::getMyItems)
    .asList()
    .map(MyItem.class::cast)
    .extracting(MyItem::getName)
    .containsExactlyInAnyOrder("foo", "bar");

注意

map()
方法。不幸的是,我们不能简单地使用
asList()
方法,因为调用
asList()
后,你会得到一个
Object
元素列表,因此你无法立即提取类型的属性。

// isn't compiled
assertThat(myClass)
    .extracting(MyClass::getMyItems)
    .asList()
    .extracting(MyItem::getName)
    .containsExactlyInAnyOrder("foo", "bar");
© www.soinside.com 2019 - 2024. All rights reserved.