Como posso afirmar que um Iterable contém elementos com uma determinada propriedade?

103

Suponha que eu queira testar a unidade de um método com esta assinatura:

List<MyItem> getMyItems();

Suponha que MyItemseja um Pojo que possui muitas propriedades, uma das quais é "name", acessada via getName().

Tudo que me importa é verificar se o List<MyItem>, ou qualquer Iterable, contém duas MyIteminstâncias, cujas "name"propriedades têm os valores "foo"e "bar". Se alguma outra propriedade não corresponder, realmente não me importo com os propósitos deste teste. Se os nomes corresponderem, o teste foi bem-sucedido.

Eu gostaria que fosse uma linha, se possível. Aqui está alguma "pseudo-sintaxe" do tipo de coisa que eu gostaria de fazer.

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

Hamcrest seria bom para esse tipo de coisa? Em caso afirmativo, qual seria exatamente a versão hamcrest da minha pseudo-sintaxe acima?

Kevin Pauli
fonte

Respostas:

125

Obrigado @Razvan que me indicou a direção certa. Consegui colocá-lo em uma linha e pesquisei com sucesso as importações para o Hamcrest 1.3.

as importações:

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;

o código:

assertThat( myClass.getMyItems(), contains(
    hasProperty("name", is("foo")), 
    hasProperty("name", is("bar"))
));
Kevin Pauli
fonte
49

Experimentar:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
Razvan
fonte
2
apenas como um nó lateral - esta é uma solução hamcrest (não assertj)
Hartmut P.
46

Não é especialmente Hamcrest, mas acho que vale a pena mencionar aqui. O que eu uso com frequência no Java8 é algo como:

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

(Editado para uma ligeira melhoria de Rodrigo Manyari. É um pouco menos prolixo. Ver comentários.)

Pode ser um pouco mais difícil de ler, mas gosto do tipo e da segurança da refatoração. Também é excelente para testar várias propriedades de feijão em combinação. por exemplo, com uma expressão && semelhante a java no filtro lambda.

Mario Eis
fonte
2
Pequena melhoria: assertTrue (myClass.getMyItems (). Stream (). AnyMatch (item -> "foo" .equals (item.getName ()));
Rodrigo Manyari
@RodrigoManyari, fechando parênteses faltando
Abdull
1
Esta solução desperdiça a possibilidade de mostrar uma mensagem de erro apropriada.
Giulio Caccin
@GiulioCaccin Acho que não. Se você usar JUnit, você pode / deve usar os métodos de asserção sobrecarregados e escrever assertTrue (..., "Minha própria mensagem de falha de teste"); Veja mais em junit.org/junit5/docs/current/api/org/junit/jupiter/api/…
Mario Eis
Quer dizer, se você fizer a asserção contra um booleano, perderá a capacidade de imprimir automaticamente a diferença real / esperada. É possível afirmar usando um matcher, mas você precisa modificar esta resposta para ser semelhante a outra nesta página para fazer isso.
Giulio Caccin
20

Assertj é bom nisso.

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

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

Uma grande vantagem para o assertj em comparação com o hamcrest é o uso fácil do auto-completar de código.

Frank Neblung
fonte
16

AssertJ fornece um excelente recurso em extracting(): você pode passar Functions para extrair campos. Ele fornece uma verificação em tempo de compilação.
Você também pode definir o tamanho primeiro facilmente.

Isso daria:

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

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

containsExactlyInAnyOrder() afirma que a lista contém apenas esses valores, seja qual for a ordem.

Para afirmar que a lista contém esses valores seja qual for a ordem, mas também pode conter outros valores, use contains():

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

Como uma observação lateral: para declarar vários campos de elementos de a List, com AssertJ fazemos isso envolvendo os valores esperados para cada elemento em uma tuple()função:

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
fonte
4
Não entendo por que isso não tem votos positivos. Acho que esta é a melhor resposta, de longe.
PeMa
1
A biblioteca assertJ é muito mais legível do que a API de asserção JUnit.
Sangimed em
@Sangimed Concordo e também prefiro ao hamcrest.
davidxxx
Na minha opinião, isso é um pouco menos legível, pois separa o "valor real" do "valor esperado" e os coloca em uma ordem que precisa corresponder.
Terran
5

Contanto que sua Lista seja uma classe concreta, você pode simplesmente chamar o método contains () desde que tenha implementado seu método equals () em 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")));

Presume que você implementou um construtor que aceita os valores que você deseja declarar. Sei que não está em uma única linha, mas é útil saber qual valor está faltando, em vez de verificar os dois ao mesmo tempo.

Brad
fonte
1
Eu realmente gosto da sua solução, mas ele deveria modificar todo o código para um teste?
Kevin Bowersox
Imagino que cada resposta aqui exigirá alguma configuração de teste, execução do método para testar e, em seguida, declarar as propriedades. Não há nenhuma sobrecarga real em minha resposta pelo que posso ver, apenas que tenho duas afirmações sobre as linhas do mar, de modo que uma afirmação com falha pode identificar claramente o valor que está faltando.
Brad
Seria melhor incluir também uma mensagem dentro de assertTrue para que a mensagem de erro seja mais inteligível. Sem uma mensagem, se falhar, o JUnit apenas lançará um AssertionFailedError sem nenhuma mensagem de erro. Portanto, é melhor incluir algo como "os resultados devem conter novo MyItem (\" foo \ ")".
Máx.
Sim você está certo. Eu recomendaria o Hamcrest de qualquer maneira, e nunca uso assertTrue () atualmente
Brad,
Por outro lado, seu POJO ou DTO deve definir o método equals
Tayab Hussain
1

AssertJ 3.9.1 suporta o uso direto de predicado no anyMatchmétodo.

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

Geralmente, esse é um caso de uso adequado para uma condição arbitrariamente complexa.

Para condições simples, prefiro usar o extractingmétodo (consulte acima) porque o iterável sob teste resultante pode oferecer suporte à verificação de valor com melhor legibilidade. Exemplo: pode fornecer API especializada, como containsmétodo na resposta de Frank Neblung. Ou você pode chamá anyMatch-lo mais tarde de qualquer maneira e usar a referência de método como "searchedvalue"::equals. Além disso, vários extratores podem ser colocados no extractingmétodo, o resultado verificado subsequentemente usando tuple().

Tomáš Záluský
fonte