Asserting properties on list elements with assertJ
我有一个可行的hamcrest断言:
1 2 3 | assertThat(mylist, contains( containsString("15"), containsString("217"))); |
预期的行为是:
-
mylist == asList("Abcd15","217aB") =>成功 -
myList == asList("Abcd15","218") =>失败
如何将这个表达式迁移到assertJ。当然,存在一些天真的解决方案,例如对第一个和第二个值进行断言,如下所示:
1 2 | assertThat(mylist.get(0)).contains("15"); assertThat(mylist.get(1)).contains("217"); |
但是这些是对列表元素的声明,而不是对列表的声明。在列表上尝试断言将我限制在非常通用的功能上。因此,也许只能使用自定义断言来解决它,如下所示就可以了:
1 2 3 | assertThat(mylist).elements() .next().contains("15") .next().contains("217") |
但是在编写自定义断言之前,我会对其他人如何解决此问题感兴趣?
编辑:另外一项非功能性要求是,该测试应易于通过其他约束条件进行扩展。在Hamcrest中,表达其他约束非常容易,例如
1 2 3 4 | assertThat(mylist, contains( emptyString(), //additional element allOf(containsString("08"), containsString("15")), //extended constraint containsString("217"))); // unchanged |
在此示例中,依赖于列表索引的测试必须重新编号,使用自定义条件的测试将必须重写完整条件(请注意,
对于此类断言,Hamcrest优于AssertJ,您可以使用条件来模仿Hamcrest,但由于AssertJ中没有提供任何开箱即用的内容,因此您需要编写它们(assertJ的理念是不与Hamcrest竞争)。
在下一个AssertJ版本(即将发布!)中,您将能够重用Hamcrest Matcher来构建AssertJ条件,例如:
1 2 3 4 5 | Condition<String> containing123 = new HamcrestCondition<>(containsString("123")); // assertions succeed assertThat("abc123").is(containing123); assertThat("def456").isNot(containing123); |
最后一点,这个建议...
1 2 3 | assertThat(mylist).elements() .next().contains("15") .next().contains("217") |
......由于泛型的限制,不幸的是,它无法工作,尽管您知道您拥有一个字符串列表,但Java泛型的功能不足以根据另一个(
实际上,您必须在assertj中实现自己的
1 2 3 | assertThat(items).has(containsExactly( stream(subItems).map(it -> containsSubstring(it)).toArray(Condition[]::new) )); |
我选择了哪种方法来满足您的要求?编写合同测试用例,然后实现未提供assertj的功能,这是我针对hamcrest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | import org.assertj.core.api.Assertions; import org.assertj.core.api.Condition; import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import java.util.Collection; import java.util.List; import static java.util.Arrays.asList; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toList; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; @RunWith(Parameterized.class) public class MatchersTest { private final SubstringExpectation expectation; public MatchersTest(SubstringExpectation expectation) { this.expectation = expectation; } @Parameters public static List<SubstringExpectation> parameters() { return asList(MatchersTest::hamcrest, MatchersTest::assertj); } private static void assertj(Collection<? extends String> items, String... subItems) { Assertions.assertThat(items).has(containsExactly(stream(subItems).map(it -> containsSubstring(it)).toArray(Condition[]::new))); } private static Condition<String> containsSubstring(String substring) { return new Condition<>(s -> s.contains(substring),"contains substring: "%s"", substring); } @SuppressWarnings("unchecked") private static <C extends Condition<? super T>, T extends Iterable<? extends E>, E> C containsExactly(Condition<E>... conditions) { return (C) new Condition< T >("contains exactly:" + stream(conditions).map(it -> it.toString()).collect(toList())) { @Override public boolean matches(T items) { int size = 0; for (E item : items) { if (!matches(item, size++)) return false; } return size == conditions.length; } private boolean matches(E item, int i) { return i < conditions.length && conditions[i].matches(item); } }; } private static void hamcrest(Collection<? extends String> items, String... subItems) { assertThat(items, contains(stream(subItems).map(Matchers::containsString).collect(toList()))); } @Test public void matchAll() { expectation.checking(asList("foo","bar"),"foo","bar"); } @Test public void matchAllContainingSubSequence() { expectation.checking(asList("foo","bar"),"fo","ba"); } @Test public void matchPartlyContainingSubSequence() { try { expectation.checking(asList("foo","bar"),"fo"); fail(); } catch (AssertionError expected) { assertThat(expected.getMessage(), containsString(""bar"")); } } @Test public void matchAgainstWithManySubstrings() { try { expectation.checking(asList("foo","bar"),"fo","ba","<many>"); fail(); } catch (AssertionError expected) { assertThat(expected.getMessage(), containsString("<many>")); } } private void fail() { throw new IllegalStateException("should failed"); } interface SubstringExpectation { void checking(Collection<? extends String> items, String... subItems); } } |
但是,您最好使用链接的
我发现最接近的是编写一个" ContainsSubstring"条件,以及一个创建该条件的静态方法,并使用
1 2 | assertThat(list).has(containsSubstring("15", atIndex(0))) .has(containsSubstring("217", atIndex(1))); |
但是也许您应该简单地编写一个循环:
1 2 3 4 5 | List<String> list = ...; List<String> expectedSubstrings = Arrays.asList("15","217"); for (int i = 0; i < list.size(); i++) { assertThat(list.get(i)).contains(expectedSubstrings.get(i)); } |
或编写一个参数化测试,以便JUnit自己在每个子字符串上测试每个元素。