Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JUnit5: How to assert several properties of an object with a single assert call?

I want to assert several properties of an object with a single assert call.

With JUnit 4 and Hamcrest I would have written something like this:

assertThat(product, allOf(
        hasProperty("name", is("Coat")),
        hasProperty("available", is(true)),
        hasProperty("amount", is(12)),
        hasProperty("price", is(new BigDecimal("88.0")))
));

Q: How to assert several properties in a single assert call using JUnit 5 and AssertJ? Or, alternatively, what is the best way of doing that in JUnit 5 universe.

Note: I can of course create an object with all needed properties and perform

assertThat(actualProduct, is(expectedProduct))

but that is not the point.

like image 517
Sasha Shpota Avatar asked Aug 13 '19 10:08

Sasha Shpota


3 Answers

With AssertJ the closest is :

assertThat(product).extracting("name", "available", "amount", "price")
                   .containsExactly("Coat", true, 12, new BigDecimal("88.0"));

But I don't like to reference field names with Strings because their validity as field names are checked only at the runtime (known issue of reflection) and that these Strings may also be incorrectly updated during refactoring operations that we perform from the IDE.

While a little more verbose, I prefer that :

assertThat(product).extracting(Product::getName, Product::getAvailable, 
                               Product::getAmount, Product::getPrice)
                   .containsExactly("Coat", true, 12, new BigDecimal("88.0"));

The advantage of AssertJ over Hamcrest that you quote is that that is really fluent : so in most of cases, you need a single import : import org.assertj.core.api.Assertions; and for collection assertions, sometimes that : org.assertj.core.groups.Tuple;

Here JUnit 5 or 4; it doesn't really matter since you will only use JUnit as a test runner for a very simple case and leave AssertJ to perform the assertions.

Or, alternatively, what is the best way of doing that in JUnit 5 universe.

JUnit 5 (as the 4th version) doesn't provide features for flexible and fluent assertions. So doing it with the best way of JUnit 5 is bound to produce much more boiler plate code : as many assertions as fields to assert or overriding equals()/hashCode() override that you want to avoid for fair reasons.

like image 167
davidxxx Avatar answered Oct 24 '22 07:10

davidxxx


With AssertJ, another option would be to use returns:

assertThat(product)
    .returns("Coat", from(Product::getName)),
    .returns(true, from(Product::getAvailable)),
    .returns(12, from(Product::getAmount)),
    .returns(new BigDecimal("88.0"), from(Product::getPrice));

A bit more verbose but I find it easier to read compared to extracting/contains.

Note that from is just an optional syntax sugar to improve readability.


Since 3.22.0, doesNotReturn is also available. This can be useful for non-null fields where the expected value is not known in advance.

assertThat(product)
    .returns("Coat", from(Product::getName)),
    .returns(true, from(Product::getAvailable)),
    .doesNotReturn(42, from(Product::getAmount)),
    .doesNotReturn(null, from(Product::getPrice));
like image 37
Stefano Cordio Avatar answered Oct 24 '22 08:10

Stefano Cordio


You could use Assertions.assertAll:

assertAll("product",
   () -> assertEquals("Coat", product.getName()),
   () -> assertTrue(product.isAvaikable())
);

assertAll can take as many individual asserts as you like. Here’s the link to the section in the user guide: https://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions

like image 24
johanneslink Avatar answered Oct 24 '22 08:10

johanneslink