或任何 Iterable 是否包含两个 MyItem 实例,它们的 "name" 属性具有值 "foo" 和 "bar"。如果任何其他属性不匹配,我真的不关心这个测试的目的。如果名称匹配,则测试......" /> 或任何 Iterable 是否包含两个 MyItem 实例,它们的 "name" 属性具有值 "foo" 和 "bar"。如果任何其他属性不匹配,我真的不关心这个测试的目的。如果名称匹配,则测试......"> 或任何 Iterable 是否包含两个 MyItem 实例,它们的 "name" 属性具有值 "foo" 和 "bar"。如果任何其他属性不匹配,我真的不关心这个测试的目的。如果名称匹配,则测试......" />
假设我想用这个签名对一个方法进行单元测试:
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 版本到底是什么?
谢谢@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"))
));
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")
);
它不是特别是 Hamcrest,但我认为这里值得一提。我在 Java8 中经常使用的是:
assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName())));
(编辑为 Rodrigo Manyari 的轻微改进。它不那么冗长。见评论。)
它可能有点难以阅读,但我喜欢这种类型和重构安全性。组合测试多个 bean 属性也很酷。例如,在过滤器 lambda 中使用类似 java 的 && 表达式。
尝试:
assertThat(myClass.getMyItems(),
hasItem(hasProperty("YourProperty", is("YourValue"))));
Assertj 擅长这一点。
import static org.assertj.core.api.Assertions.assertThat;
assertThat(myClass.getMyItems()).extracting("name").contains("foo", "bar");
与 hamcrest 相比,assertj 的一大优点是易于使用代码完成。
只要您的 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")));
假设您已经实现了一个构造函数,该构造函数接受您要断言的值。我意识到这不是在单行上,但是知道缺少哪个值而不是同时检查两个值很有用。
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()
验证结果。
除了 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
使用 Stream,您还可以:
List<String> actual = myList.stream().map(MyClass::getName).collect(toList());
assertThat(actual, hasItem("expectedString1"));
因为使用 anyMatch()
或 allMatch()
,您知道列表中的 一些 值在列表中,但是您的实际列表可能只包含 5 个值,而在 anyMatch()
中您有 6 个;您不知道是否存在 所有 值。使用 hasItem()
,您确实可以检查您想要的每个值。
containsInAnyOrder
(来自同一父类):)