假设我想使用此签名对方法进行单元测试:
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.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")
);
它不是特别是Hamcrest,但我认为这里值得一提。我在 Java8 中经常使用的是这样的:
assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName())));
(编辑 Rodrigo Manyari 的轻微改进。稍微不那么冗长。请参阅评论。)
它可能有点难以阅读,但我喜欢它的类型和重构安全性。 组合测试多种豆子特性也很酷。例如在过滤器 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
方法(见上文),因为结果可迭代的测试可能支持具有更好可读性的值验证。
例如:它可以提供专门的API,例如Frank Neblung的答案中的contains
方法。或者您可以稍后调用 anyMatch
并使用方法引用,例如 "searchedvalue"::equals
。也可以将多个提取器放入extracting
方法中,随后使用tuple()
验证结果。
使用 Stream,您还可以执行以下操作:
List<String> actual = myList.stream().map(MyClass::getName).collect(toList());
assertThat(actual, hasItem("expectedString1"));
因为使用
anyMatch()
或 allMatch()
,您知道列表中的 some 值在列表中,但您的实际列表可能只包含 5 个值,而在 anyMatch()
中您有 6 个值;您不知道all值是否存在。使用 hasItem()
,您确实可以检查您想要的每个值。
除了
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"))
));
这种方法的优点是:
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"
有时我们需要断言一个包含
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");