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

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

List<MyItem> getMyItems();

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

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

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

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

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


person Kevin Pauli    schedule 28.08.2012    source источник


Ответы (8)


Спасибо @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"))
));
person Kevin Pauli    schedule 28.08.2012

Пытаться:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
person Razvan    schedule 28.08.2012
comment
так же, как боковой узел - это решение hamcrest (не assertj) - person Hartmut P.; 10.10.2016

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

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

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

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

person Mario Eis    schedule 19.10.2015
comment
Небольшое улучшение: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item - ›foo.equals (item.getName ())); - person Rodrigo Manyari; 11.05.2016
comment
@RodrigoManyari, закрывающая скобка отсутствует - person Abdull; 19.10.2016
comment
Это решение тратит возможность показать соответствующее сообщение об ошибке. - person Giulio Caccin; 06.05.2019
comment
@GiulioCaccin Не думаю, что это так. Если вы используете JUnit, вы можете / должны использовать перегруженные методы утверждения и написать assertTrue (..., Мое собственное сообщение об ошибке теста); См. Больше на junit.org/junit5/docs/current/api/org/junit/jupiter/api/ - person Mario Eis; 06.05.2019
comment
Я имею в виду, что если вы выполняете утверждение против логического значения, вы теряете возможность автоматически печатать фактическую / ожидаемую разницу. Можно утверждать, используя сопоставитель, но для этого вам нужно изменить этот ответ, чтобы он был похож на другие на этой странице. - person Giulio Caccin; 06.05.2019
comment
Хм, я не могу следить за тобой. Здесь нет никакой разницы или сравнения. Вопрос был в том, содержит ли коллекция предметы или нет. Так что просто выполните assertTrue (..., Коллекция не содержит элементов с именем 'foo'), и в случае сбоя ваш тестовый отчет будет содержать правильное сообщение. Если ваше сообщение по какой-либо причине должно содержать тестовые данные, просто добавьте их в сообщение. - person Mario Eis; 07.05.2019

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

В качестве побочного примечания: чтобы утверждать несколько полей из элементов 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")
           ); 
person davidxxx    schedule 12.08.2018
comment
Не понимаю, почему у этого нет голосов. Я думаю, что это, безусловно, лучший ответ. - person PeMa; 06.10.2018
comment
Библиотека assertJ гораздо более читабельна, чем API утверждений JUnit. - person Sangimed; 27.12.2018
comment
@Sangimed Согласен, и я предпочитаю его хамкресту. - person davidxxx; 31.12.2018
comment
На мой взгляд, это немного менее читаемо, поскольку оно отделяет фактическое значение от ожидаемого и размещает их в порядке, который должен совпадать. - person Terran; 13.05.2020

Assertj в этом хорошо разбирается.

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

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

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

person Frank Neblung    schedule 22.12.2015

Пока ваш 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")));

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

person Brad    schedule 28.08.2012
comment
Мне действительно нравится ваше решение, но стоит ли ему модифицировать весь этот код для теста? - person Kevin Bowersox; 28.08.2012
comment
Я полагаю, что каждый ответ здесь потребует некоторой настройки теста, выполнения метода для тестирования, а затем утверждения свойств. Из того, что я вижу, в моем ответе нет реальных накладных расходов, только то, что у меня есть два утверждения в строках seaprate, так что неудавшееся утверждение может четко определить, какое значение отсутствует. - person Brad; 29.08.2012
comment
Было бы лучше также включить сообщение в assertTrue, чтобы сообщение об ошибке было более понятным. Без сообщения, если это не удается, JUnit просто выдаст AssertionFailedError без какого-либо сообщения об ошибке. Поэтому лучше всего включать что-то вроде результатов, которые должны содержать новый MyItem (\ foo \). - person Max; 07.04.2017
comment
Да, ты прав. Я бы порекомендовал Hamcrest в любом случае, и в наши дни я никогда не использую assertTrue () - person Brad; 07.04.2017
comment
Кстати, ваш POJO или DTO должен определять метод equals - person Tayab Hussain; 13.09.2018

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

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

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

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

person Tomáš Záluský    schedule 08.11.2019

В качестве альтернативы 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.
person seregamorph    schedule 09.10.2020