171

Assume I want to unit test a method with this signature:

List<MyItem> getMyItems();

Assume MyItem is a Pojo that has many properties, one of which is "name", accessed via getName().

All I care about verifying is that the List<MyItem>, or any Iterable, contains two MyItem instances, whose "name" properties have the values "foo" and "bar". If any other properties don't match, I don't really care for the purposes of this test. If the names match, it's a successful test.

I would like it to be one-liner if possible. Here is some "pseudo-syntax" of the kind of thing I would like to do.

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

Would Hamcrest be good for this type of thing? If so, what exactly would be the hamcrest version of my pseudo-syntax above?

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Kevin Pauli
  • 8,577
  • 15
  • 49
  • 70

9 Answers9

165

Thank you @Razvan who pointed me in the right direction. I was able to get it in one line and I successfully hunted down the imports for Hamcrest 1.3.

the imports:

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;

the code:

assertThat( myClass.getMyItems(), contains(
    hasProperty("name", is("foo")), 
    hasProperty("name", is("bar"))
));
PiroXXI
  • 47
  • 1
  • 7
Kevin Pauli
  • 8,577
  • 15
  • 49
  • 70
111

AssertJ provides an excellent feature in extracting() : you can pass Functions to extract fields. It provides a check at compile time.
You could also assert the size first easily.

It would give :

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

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

containsExactlyInAnyOrder() asserts that the list contains only these values whatever the order.

To assert that the list contains these values whatever the order but may also contain other values use contains() :

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

As a side note : to assert multiple fields from elements of a List , with AssertJ we do that by wrapping expected values for each element into a tuple() function :

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;

assertThat(myClass.getMyItems())
          .hasSize(2)
          .extracting(MyItem::getName, MyItem::getOtherValue)
          .containsExactlyInAnyOrder(
               tuple("foo", "OtherValueFoo"),
               tuple("bar", "OtherValueBar")
           ); 
sfelber
  • 888
  • 8
  • 23
davidxxx
  • 125,838
  • 23
  • 214
  • 215
64

Its not especially Hamcrest, but I think it worth to mention here. What I use quite often in Java8 is something like:

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

(Edited to Rodrigo Manyari's slight improvement. It's a little less verbose. See comments.)

It may be a little bit harder to read, but I like the type and refactoring safety. Its also cool for testing multiple bean properties in combination. e.g. with a java-like && expression in the filter lambda.

Mario Eis
  • 2,724
  • 31
  • 32
  • 3
    Slight improvement: assertTrue(myClass.getMyItems().stream().anyMatch(item -> "foo".equals(item.getName())); – Rodrigo Manyari May 11 '16 at 02:15
  • @RodrigoManyari, closing parenthesis missing – Abdull Oct 19 '16 at 11:43
  • 6
    This solution waste the possibility to show an appropriate error message. – Giulio Caccin May 06 '19 at 11:43
  • @GiulioCaccin I don't think it does. If you use JUnit, you could/should use the overloaded assertion methods and write assertTrue(..., "My own test failure message"); See more on https://junit.org/junit5/docs/current/api/org/junit/jupiter/api/Assertions.html#assertTrue(boolean,java.lang.String) – Mario Eis May 06 '19 at 19:30
  • 2
    I mean, if you do the assertion against a Boolean, you lose the ability to print automatically the actual/expected difference. It is possible to assert using a matcher, but you need to modify this response to be similar to other in this page to do it. – Giulio Caccin May 06 '19 at 19:50
  • Hm, I can't follow you. There is no difference or comparison here. The Question was, whether the collection contains items or not. So just do assertTrue(..., "The collection doesn't contain items with the name 'foo'") and in case of a failure, your test report will contain the correct message. If your message, for any reason, has to contain the test data, just add it to the massage. – Mario Eis May 07 '19 at 10:21
58

Try:

assertThat(myClass.getMyItems(),
                          hasItem(hasProperty("YourProperty", is("YourValue"))));
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
Razvan
  • 9,925
  • 6
  • 38
  • 51
31

Assertj is good at this.

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

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

Big plus for assertj compared to hamcrest is easy use of code completion.

Frank Neblung
  • 3,047
  • 17
  • 34
8

As long as your List is a concrete class, you can simply call the contains() method as long as you have implemented your equals() method on 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")));

Assumes you have implemented a constructor that accepts the values you want to assert on. I realise this isn't on a single line, but it's useful to know which value is missing rather than checking both at once.

Brad
  • 15,186
  • 11
  • 60
  • 74
  • 1
    I really like your solution, but should he mod all that code for a test? – Kevin Bowersox Aug 28 '12 at 19:55
  • I figure that every answer here will require some test setup, execution of the method to test, and then assert the properties. There's no real overhead to my answer from what I can see, only that I have two assertions on seaprate lines so that a failed assertion can clearly identify what value is missing. – Brad Aug 28 '12 at 20:36
  • It would be best to also include a message within assertTrue so that the error message is more intelligible. Without a message, if it fails, JUnit will just throw a AssertionFailedError without any error message. So best to include something like "results should contain new MyItem(\"foo\")". – Max Apr 07 '17 at 12:46
  • Yes you are right. I'd recommend Hamcrest in any case, and I never use assertTrue() these days – Brad Apr 07 '17 at 17:52
  • To a side note your POJO or DTO should define the equals method – Tayab Hussain Sep 13 '18 at 14:23
5

AssertJ 3.9.1 supports direct predicate usage in anyMatch method.

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

This is generally suitable use case for arbitrarily complex condition.

For simple conditions I prefer using extracting method (see above) because resulting iterable-under-test might support value verification with better readability. Example: it can provide specialized API such as contains method in Frank Neblung's answer. Or you can call anyMatch on it later anyway and use method reference such as "searchedvalue"::equals. Also multiple extractors can be put into extracting method, result subsequently verified using tuple().

Tomáš Záluský
  • 10,735
  • 2
  • 36
  • 64
0

Alternatively to hasProperty you can try hamcrest-more-matchers where matcher with extracting function. In your case it will look like:

import static com.github.seregamorph.hamcrest.MoreMatchers.where;

assertThat(myClass.getMyItems(), contains(
    where(MyItem::getName, is("foo")), 
    where(MyItem::getName, is("bar"))
));

The advantages of this approach are:

  • It is not always possible to verify by field if the value is computed in get-method
  • In case of mismatch there should be a failure message with diagnostics (pay attention to resolved method reference 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"
  • It works in Java 8, Java 11 and Java 14
seregamorph
  • 413
  • 3
  • 5
0

With Stream you can also do:

List<String> actual = myList.stream().map(MyClass::getName).collect(toList());
assertThat(actual, hasItem("expectedString1"));

Because with anyMatch() or allMatch(), you know some values in your list are in the list, but there is possibility that your actual list only contains 5 values while in anyMatch() you have 6; you don't know if all values are present or not. With hasItem(), you indeed check every value you want.

WesternGun
  • 11,303
  • 6
  • 88
  • 157