Как мне утверждать, что Iterable содержит элементы с определенным свойством?

103

Предположим, я хочу провести модульное тестирование метода с этой подписью:

List<MyItem> getMyItems();

Предположим MyItem, что Pojo имеет множество свойств, одно из которых "name"доступно через getName().

Все, что мне нужно для проверки, это то, что объект List<MyItem>или any Iterableсодержит два MyItemэкземпляра, "name"свойства которых имеют значения "foo"и "bar". Если какие-либо другие свойства не совпадают, мне наплевать на цели этого теста. Если имена совпадают, это успешный тест.

Я бы хотел, чтобы он был однострочным, если это возможно. Вот какой-то «псевдосинтаксис» того, чем я хотел бы заниматься.

assert(listEntriesMatchInAnyOrder(myClass.getMyItems(), property("name"), new String[]{"foo", "bar"});

Подойдет ли Хамкрест для таких вещей? Если да, то какой именно будет версия моего псевдосинтаксиса, описанного выше?

Кевин Паули
источник

Ответы:

125

Спасибо @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"))
));
Кевин Паули
источник
49

Пытаться:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
Разван
источник
2
просто как боковой узел - это хамкрестное решение (не assertj)
Хартмут П.
46

Это не особенно Hamcrest, но я думаю, что стоит упомянуть здесь. То, что я довольно часто использую в Java8, выглядит примерно так:

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

(Отредактировано для небольшого улучшения Родриго Маньяри. Это немного менее многословно. См. Комментарии.)

Это может быть немного сложнее читать, но мне нравится тип и безопасность рефакторинга. Это также здорово для одновременного тестирования нескольких свойств bean-компонентов. например, с java-подобным выражением && в лямбда-фильтре.

Марио Эйс
источник
2
Небольшое улучшение: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item -> "foo" .equals (item.getName ()));
Родриго Маньяри
@RodrigoManyari, закрывающая скобка отсутствует
Абдулл
1
Это решение тратит возможность показать соответствующее сообщение об ошибке.
Джулио Каччин
@GiulioCaccin Не думаю, что это так. Если вы используете JUnit, вы можете / должны использовать перегруженные методы утверждения и написать assertTrue (..., «Мое собственное сообщение об ошибке теста»); См. Дополнительную информацию на junit.org/junit5/docs/current/api/org/junit/jupiter/api/…
Марио Эйс,
Я имею в виду, что если вы выполняете утверждение против логического значения, вы теряете возможность автоматически печатать фактическую / ожидаемую разницу. Можно утверждать, используя сопоставитель, но для этого вам нужно изменить этот ответ, чтобы он был похож на другой на этой странице.
Джулио Каччин
20

Assertj хорош в этом.

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

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

Большой плюс assertj по сравнению с hamcrest - простота использования автозавершения кода.

Фрэнк Неблунг
источник
16

AssertJ предоставляет отличную возможность extracting(): вы можете передавать Functions для извлечения полей. Он обеспечивает проверку во время компиляции.
Вы также можете легко сначала указать размер.

Это даст:

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

Assertions.assertThat(myClass.getMyItems())
          .hasSize(2)
          .extracting(MyItem::getName)
          .containsExactlyInAnyOrder("foo", "bar"); 

containsExactlyInAnyOrder() утверждает, что список содержит только эти значения в любом порядке.

Чтобы утверждать, что список содержит эти значения в любом порядке, но может также содержать другие значения, используйте contains():

.contains("foo", "bar"); 

В качестве побочного примечания: чтобы утверждать несколько полей из элементов a 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")
           ); 
davidxxx
источник
4
Не понимаю, почему у этого нет голосов. Я думаю, что это, безусловно, лучший ответ.
PeMa 06
1
Библиотека assertJ гораздо более читабельна, чем API утверждений JUnit.
Sangimed
@Sangimed Согласен, и я предпочитаю его хамкресту.
davidxxx
На мой взгляд, это немного менее читаемо, поскольку оно отделяет «фактическое значение» от «ожидаемого значения» и помещает их в порядке, который должен совпадать.
Терран
5

Пока ваш List является конкретным классом, вы можете просто вызвать метод contains (), если вы реализовали свой метод equals () в MyItem.

// 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")));

Предполагается, что вы реализовали конструктор, который принимает значения, которые вы хотите утверждать. Я понимаю, что это не в одной строке, но полезно знать, какое значение отсутствует, а не проверять оба сразу.

Брэд
источник
1
Мне действительно нравится ваше решение, но стоит ли ему модифицировать весь этот код для теста?
Кевин Бауэрсокс,
Я полагаю, что каждый ответ здесь потребует некоторой настройки теста, выполнения метода для тестирования, а затем утверждения свойств. Из того, что я вижу, в моем ответе нет реальных накладных расходов, только то, что у меня есть два утверждения в строках seaprate, так что неудавшееся утверждение может четко определить, какое значение отсутствует.
Брэд
Было бы лучше также включить сообщение в assertTrue, чтобы сообщение об ошибке было более понятным. Без сообщения, в случае сбоя, JUnit просто выдаст AssertionFailedError без какого-либо сообщения об ошибке. Поэтому лучше всего включить что-то вроде «результаты должны содержать новый MyItem (\" foo \ ")».
Макс
Да ты прав. Я бы порекомендовал Hamcrest в любом случае, и в наши дни я никогда не использую assertTrue ()
Брэд,
Кстати, ваш POJO или DTO должен определять метод equals
Тайаб Хуссейн,
1

AssertJ 3.9.1 поддерживает прямое использование предикатов в anyMatchметоде.

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

Обычно это подходящий вариант использования для произвольно сложных условий.

Для простых условий я предпочитаю использовать extractingметод (см. Выше), потому что результирующая итерация при тестировании может поддерживать проверку значений с лучшей читабельностью. Пример: он может предоставлять специализированный API, такой как containsметод в ответе Фрэнка Неблунга. Или вы anyMatchвсе равно можете вызвать его позже и использовать ссылку на метод, например "searchedvalue"::equals. Также в extractingметод могут быть включены несколько экстракторов , результат впоследствии проверяется с помощью tuple().

Томаш Залуски
источник