junit 使用assertJAssert列表元素的属性

c0vxltue  于 2022-11-11  发布在  其他
关注(0)|答案(6)|浏览(144)

我有一个工作hamcrestAssert:

assertThat(mylist, contains(
  containsString("15"), 
  containsString("217")));

预期行为为:

  • 成功了
  • myList == asList("Abcd15", "218") =〉故障

我如何将这个表达式移植到assertJ中呢?当然,也存在一些简单的解决方案,比如对第一个值和第二个值进行Assert,如下所示:

assertThat(mylist.get(0)).contains("15");
assertThat(mylist.get(1)).contains("217");

但是这些是列表元素上的Assert,而不是列表上的Assert。尝试列表上的Assert会限制我使用非常通用的函数。因此,可能只能通过自定义Assert来解决,类似于以下内容就可以了:

assertThat(mylist).elements()
  .next().contains("15")
  .next().contains("217")

但是在我写一个自定义Assert之前,我会对其他人将如何解决这个问题感兴趣?
编辑:一个额外的非功能性要求是,测试应该很容易通过额外的约束进行扩展。

assertThat(mylist, contains(
  emptyString(),                                     //additional element
  allOf(containsString("08"), containsString("15")), //extended constraint
  containsString("217")));                           // unchanged

依赖于列表索引的测试必须在本例中重新编号,使用自定义条件的测试必须重写整个条件(注意,allOf中的约束不限于子字符串检查)。

yrdbyhpb

yrdbyhpb1#

对于这类Assert,Hamcrest上级AssertJ,您可以使用条件来模仿Hamcrest,但您需要编写它们,因为AssertJ中没有现成的条件(assertJ philosphy在这方面不与Hamcrest竞争)。
在下一个AssertJ版本(即将发布!)中,您将能够重用Hamcrest Matcher来构建AssertJ条件,例如:

Condition<String> containing123 = new HamcrestCondition<>(containsString("123"));

// assertions succeed
assertThat("abc123").is(containing123);
assertThat("def456").isNot(containing123);

最后一点,这个建议...

assertThat(mylist).elements()
                  .next().contains("15")
                  .next().contains("217")

...不幸的是,由于泛型的限制而无法工作,尽管你知道你有一个String列表,Java泛型还没有强大到足以根据另一个(String)来选择一个特定类型(StringAssert),这意味着你只能对元素执行ObjectAssert,而不能执行StringAssert。

  • 编辑-
    由于Python 3.13.0可以使用asInstanceOf来获取特定的类型Assert,因此如果声明的类型是Object,但运行时类型更具体,这将非常有用。
    示例:
// Given a String declared as an Object
Object value = "Once upon a time in the west";

// With asInstanceOf, we switch to specific String assertion by specifying the InstanceOfAssertFactory for String
assertThat(value).asInstanceOf(InstanceOfAssertFactories.STRING)
                 .startsWith("Once");`

请https://assertj.github.io/doc/#assertj-core-3.13.0-asInstanceOf

cs7cruho

cs7cruho2#

您可以使用anyMatch

assertThat(mylist)
  .anyMatch(item -> item.contains("15"))
  .anyMatch(item -> item.contains("217"))

但不幸的是,失败消息无法告诉您有关预期的内部信息

Expecting any elements of:
  <["Abcd15", "218"]>
to match given predicate but none did.
pjngdqdw

pjngdqdw3#

我找到的最接近的方法是编写一个“ContainsSubstring”条件,并使用一个静态方法来创建一个条件,然后使用

assertThat(list).has(containsSubstring("15", atIndex(0)))
                .has(containsSubstring("217", atIndex(1)));

但也许您应该简单地编写一个循环:

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本身在每个子字符串上测试每个元素。

s8vozzvw

s8vozzvw4#

您可以执行以下操作:

List<String> list1 = Arrays.asList("Abcd15", "217aB");
List<String> list2 = Arrays.asList("Abcd15", "218");

Comparator<String> containingSubstring = (o1, o2) -> o1.contains(o2) ? 0 : 1;
assertThat(list1).usingElementComparator(containingSubstring).contains("15", "217");  // passes
assertThat(list2).usingElementComparator(containingSubstring).contains("15", "217");  // fails

它给出的错误是:

java.lang.AssertionError: 
Expecting:
 <["Abcd15", "218"]>
to contain:
 <["15", "217"]>
but could not find:
 <["217"]>
af7jpaap

af7jpaap5#

AssertJ v3.19.0或更高版本:请使用satisfiesExactly

2021年发布的AssertJ v3.19.0增加了一个satisfiesExactly方法。
所以你可以写:

assertThat(mylist)
    .satisfiesExactly(item1 -> assertThat(item1).contains("15"),
                      item2 -> assertThat(item2).contains("217"));

如果需要,可以向单个元素添加更多Assert:

assertThat(mylist)
    .satisfiesExactly(item1 -> assertThat(item1)
                                      .contains("08")
                                      .contains("15"),
                      item2 -> assertThat(item2).contains("217"));

与使用next()链的技术相比,这个技术还可以检查列表的大小。作为一个额外的好处,它允许你使用任何你喜欢的lambda参数,所以更容易阅读和跟踪你在哪个元素中。

zz2j4svz

zz2j4svz6#

实际上,您必须在assertj中实现您自己的Condition,以便按顺序检查包含子字符串的集合。例如:

assertThat(items).has(containsExactly(
  stream(subItems).map(it -> containsSubstring(it)).toArray(Condition[]::new)
));

我选择了什么方法来满足您的需求?编写一个contract测试用例,然后实现assertj没有给出的功能,下面是我的测试用例,用于hamcrest contains(containsString(...)) adapt to assertj containsExactly,如下所示:

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

但是,您可能会使用chained Condition而不是assertj fluent api,所以我建议您尝试使用hamcrest来代替。换句话说,如果您在assertj中使用这种样式,您必须编写许多Condition或修改hamcrest Matcher以使assertj Condition

相关问题