diff --git a/src/main/java/org/assertj/core/api/AbstractIterableAssert.java b/src/main/java/org/assertj/core/api/AbstractIterableAssert.java index 062c1e868b..3416dd094a 100644 --- a/src/main/java/org/assertj/core/api/AbstractIterableAssert.java +++ b/src/main/java/org/assertj/core/api/AbstractIterableAssert.java @@ -38,6 +38,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.SortedSet; import java.util.TreeMap; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -53,7 +54,6 @@ import org.assertj.core.api.iterable.ThrowingExtractor; import org.assertj.core.condition.Not; import org.assertj.core.description.Description; -import org.assertj.core.extractor.Extractors; import org.assertj.core.groups.FieldsOrPropertiesExtractor; import org.assertj.core.groups.Tuple; import org.assertj.core.internal.CommonErrors; @@ -835,7 +835,7 @@ public AbstractListAssert, Object, ObjectAssert values = FieldsOrPropertiesExtractor.extract(actual, byName(propertyOrField)); String extractedDescription = extractedDescriptionOf(propertyOrField); String description = mostRelevantDescription(info.description(), extractedDescription); - return newListAssertInstance(values).withAssertionState(myself).as(description); + return newListAssertInstanceForMethodsChangingElementType(values).as(description); } /** @@ -883,7 +883,7 @@ public AbstractListAssert, Object, ObjectAssert values = FieldsOrPropertiesExtractor.extract(actual, resultOf(method)); String extractedDescription = extractedDescriptionOfMethod(method); String description = mostRelevantDescription(info.description(), extractedDescription); - return newListAssertInstance(values).withAssertionState(myself).as(description); + return newListAssertInstanceForMethodsChangingElementType(values).as(description); } /** @@ -934,7 +934,7 @@ public

AbstractListAssert, P, ObjectAssert

> extracti List

values = (List

) FieldsOrPropertiesExtractor.extract(actual, resultOf(method)); String extractedDescription = extractedDescriptionOfMethod(method); String description = mostRelevantDescription(info.description(), extractedDescription); - return newListAssertInstance(values).withAssertionState(myself).as(description); + return newListAssertInstanceForMethodsChangingElementType(values).as(description); } /** @@ -1025,7 +1025,7 @@ public

AbstractListAssert, P, ObjectAssert

> extracti List

values = (List

) FieldsOrPropertiesExtractor.extract(actual, byName(propertyOrField)); String extractedDescription = extractedDescriptionOf(propertyOrField); String description = mostRelevantDescription(info.description(), extractedDescription); - return newListAssertInstance(values).withAssertionState(myself).as(description); + return newListAssertInstanceForMethodsChangingElementType(values).as(description); } /** @@ -1117,7 +1117,7 @@ public AbstractListAssert, Tuple, ObjectAssert> List values = FieldsOrPropertiesExtractor.extract(actual, byName(propertiesOrFields)); String extractedDescription = extractedDescriptionOf(propertiesOrFields); String description = mostRelevantDescription(info.description(), extractedDescription); - return newListAssertInstance(values).withAssertionState(myself).as(description); + return newListAssertInstanceForMethodsChangingElementType(values).as(description); } /** @@ -1163,7 +1163,7 @@ public AbstractListAssert, Tuple, ObjectAssert> @CheckReturnValue public AbstractListAssert, V, ObjectAssert> extracting(Extractor extractor) { List values = FieldsOrPropertiesExtractor.extract(actual, extractor); - return newListAssertInstance(values).withAssertionState(myself); + return newListAssertInstanceForMethodsChangingElementType(values); } /** @@ -1209,6 +1209,20 @@ public AbstractListAssert, V, ObjectAssert> extracti @CheckReturnValue public AbstractListAssert, V, ObjectAssert> extracting(ThrowingExtractor extractor) { List values = FieldsOrPropertiesExtractor.extract(actual, extractor); + return newListAssertInstanceForMethodsChangingElementType(values); + } + + /** + * Should be used after any methods changing the elements type like {@link #extracting(Extractor)} as it will propagate the correct + * assertions state, that is everyting but the element comparator (since the element type has changed). + */ + private AbstractListAssert, V, ObjectAssert> newListAssertInstanceForMethodsChangingElementType(List values) { + if (actual instanceof SortedSet) { + // Reset the natural element comparator set when building an iterable assert instance for a SortedSet as it is likely not + // compatible with extracted values type, example with a SortedSet using a comparator on the Person's age, after + // extracting names we get a a List which is mot suitable for the age comparator + usingDefaultElementComparator(); + } return newListAssertInstance(values).withAssertionState(myself); } @@ -1297,8 +1311,10 @@ public AbstractListAssert, } private AbstractListAssert, V, ObjectAssert> doFlatExtracting(Extractor> extractor) { - List result = FieldsOrPropertiesExtractor.extract(actual, extractor).stream().flatMap(Collection::stream).collect(toList()); - return newListAssertInstance(result).withAssertionState(myself); + List result = FieldsOrPropertiesExtractor.extract(actual, extractor).stream() + .flatMap(Collection::stream) + .collect(toList()); + return newListAssertInstanceForMethodsChangingElementType(result); } /** @@ -1332,7 +1348,7 @@ public AbstractListAssert, Object, ObjectAssert result = actualStream.flatMap(element -> Stream.of(extractors) .map(extractor -> extractor.extract(element))) .collect(Collectors.toList()); - return newListAssertInstance(result).withAssertionState(myself); + return newListAssertInstanceForMethodsChangingElementType(result); } /** @@ -1376,7 +1392,7 @@ public AbstractListAssert result = actualStream.flatMap(element -> Stream.of(extractors) .map(extractor -> extractor.extract(element))) .collect(Collectors.toList()); - return newListAssertInstance(result).withAssertionState(myself); + return newListAssertInstanceForMethodsChangingElementType(result); } /** @@ -1428,7 +1444,7 @@ public AbstractListAssert, Object, ObjectAssert, Tuple, ObjectAssert> .toArray()); List tuples = stream(actual.spliterator(), false).map(tupleExtractor) .collect(toList()); - return newListAssertInstance(tuples).withAssertionState(myself); + return newListAssertInstanceForMethodsChangingElementType(tuples); } /** @@ -1516,11 +1532,10 @@ public AbstractListAssert, Tuple, ObjectAssert> */ @CheckReturnValue public AbstractListAssert, Object, ObjectAssert> flatExtracting(String... fieldOrPropertyNames) { - List extractedValues = FieldsOrPropertiesExtractor.extract(actual, Extractors.byName(fieldOrPropertyNames)) - .stream() + List extractedValues = FieldsOrPropertiesExtractor.extract(actual, byName(fieldOrPropertyNames)).stream() .flatMap(tuple -> tuple.toList().stream()) .collect(toList()); - return newListAssertInstance(extractedValues).withAssertionState(myself); + return newListAssertInstanceForMethodsChangingElementType(extractedValues); } /** diff --git a/src/main/java/org/assertj/core/api/AbstractObjectAssert.java b/src/main/java/org/assertj/core/api/AbstractObjectAssert.java index 99bf48294b..986419f66e 100644 --- a/src/main/java/org/assertj/core/api/AbstractObjectAssert.java +++ b/src/main/java/org/assertj/core/api/AbstractObjectAssert.java @@ -13,6 +13,7 @@ package org.assertj.core.api; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.description.Description.mostRelevantDescription; import static org.assertj.core.extractor.Extractors.byName; import static org.assertj.core.extractor.Extractors.extractedDescriptionOf; @@ -26,6 +27,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.assertj.core.api.iterable.Extractor; import org.assertj.core.description.Description; import org.assertj.core.groups.Tuple; import org.assertj.core.internal.TypeComparators; @@ -218,7 +220,7 @@ public SELF isEqualToIgnoringGivenFields(Object other, String... propertiesOrFie * @return {@code this} assertion object. * @throws AssertionError if the actual object is {@code null}. * @throws AssertionError if some fields or properties of the actual object are null. - * + * * @since 2.5.0 / 3.5.0 */ public SELF hasNoNullFieldsOrProperties() { @@ -249,7 +251,7 @@ public SELF hasNoNullFieldsOrProperties() { * @return {@code this} assertion object. * @throws AssertionError if the actual object is {@code null}. * @throws AssertionError if some (non ignored) fields or properties of the actual object are null. - * + * * @since 2.5.0 / 3.5.0 */ public SELF hasNoNullFieldsOrPropertiesExcept(String... propertiesOrFieldsToIgnore) { @@ -310,8 +312,8 @@ protected TypeComparators getComparatorsByType() { *

* The comparators specified by this method are only used for field by field comparison like {@link #isEqualToComparingFieldByField(Object)}. *

- * When used with {@link #isEqualToComparingFieldByFieldRecursively(Object)}, the fields/properties must be specified from the root object, - * for example if Foo class as a Bar field and Bar class has an id, to set a comparator for Bar's id use {@code "bar.id"}. + * When used with {@link #isEqualToComparingFieldByFieldRecursively(Object)}, the fields/properties must be specified from the root object, + * for example if Foo class as a Bar field and Bar class has an id, to set a comparator for Bar's id use {@code "bar.id"}. *

* Example: *

 public class TolkienCharacter {
@@ -402,7 +404,7 @@ public  SELF usingComparatorForFields(Comparator comparator, String... pro
    * assertThat(frodo).usingComparatorForType(closeEnough, Double.class)
    *                  .isEqualToComparingFieldByField(reallyTallFrodo);
* - * If multiple compatible comparators have been registered for a given {@code type}, the closest in the inheritance + * If multiple compatible comparators have been registered for a given {@code type}, the closest in the inheritance * chain to the given {@code type} is chosen in the following order: *
    *
  1. The comparator for the exact given {@code type}
  2. @@ -524,7 +526,7 @@ public SELF hasFieldOrPropertyWithValue(String name, Object value) { *

    * Private fields can be extracted unless you call {@link Assertions#setAllowExtractingPrivateFields(boolean) Assertions.setAllowExtractingPrivateFields(false)}. *

    - * If the object under test is a {@link Map} with {@link String} keys, extracting will extract values matching the given fields/properties. + * If the object under test is a {@link Map} with {@link String} keys, extracting will extract values matching the given fields/properties. *

    * Example: *

     // Create frodo, setting its name, age and Race (Race having a name property)
    @@ -552,18 +554,18 @@ public AbstractListAssert, Object, ObjectAssert
    -   * If the given {@link Function}s extract the id, name and email values then the list will contain the id, name and email values 
    +   * If the given {@link Function}s extract the id, name and email values then the list will contain the id, name and email values
        * of the object under test, you can then perform list assertions on the extracted values.
        * 

    * Example: *

     // Create frodo, setting its name, age and Race (Race having a name property)
        * TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
    -   * 
    +   *
        * // let's verify Frodo's name, age and race name:
    -   * assertThat(frodo).extracting(TolkienCharacter::getName, 
    +   * assertThat(frodo).extracting(TolkienCharacter::getName,
        *                              character -> character.age, // public field
        *                              character -> character.getRace().getName())
        *                  .containsExactly("Frodo", 33, "Hobbit");
    @@ -581,6 +583,14 @@ public AbstractListAssert, Object, ObjectAssert extracting(Extractor extractor) { + return null; + } + + public void should_testName() { + assertThat(new Object()).extracting(s -> s); + } + /** * Assert that the object under test (actual) is equal to the given object based on recursive a property/field by property/field comparison (including * inherited ones). This can be useful if actual's {@code equals} implementation does not suit you. @@ -657,13 +667,13 @@ public SELF isEqualToComparingFieldByFieldRecursively(Object other) { } /** - * Verify that the object under test returns the given expected value from the given {@link Function}, + * Verify that the object under test returns the given expected value from the given {@link Function}, * a typical usage is to pass a method reference to assert object's property. *

    * Wrapping the given {@link Function} with {@link Assertions#from(Function)} makes the assertion more readable. *

    * Example: - *

     // from is not mandatory but it makes the assertions more readable 
    +   * 
     // from is not mandatory but it makes the assertions more readable
        * assertThat(frodo).returns("Frodo", from(TolkienCharacter::getName))
        *                  .returns("Frodo", TolkienCharacter::getName) // no from :(
        *                  .returns(HOBBIT, from(TolkienCharacter::getRace));
    diff --git a/src/test/java/org/assertj/core/api/iterable/IterableAssert_extractingResultOf_with_SortedSet_Test.java b/src/test/java/org/assertj/core/api/iterable/IterableAssert_extractingResultOf_with_SortedSet_Test.java new file mode 100644 index 0000000000..67a613c507 --- /dev/null +++ b/src/test/java/org/assertj/core/api/iterable/IterableAssert_extractingResultOf_with_SortedSet_Test.java @@ -0,0 +1,157 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * Copyright 2012-2018 the original author or authors. + */ +package org.assertj.core.api.iterable; + +import static java.util.Comparator.comparing; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorForElementFieldsWithNamesOf; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorForElementFieldsWithTypeOf; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorsByTypeOf; +import static org.assertj.core.presentation.UnicodeRepresentation.UNICODE_REPRESENTATION; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_STRING; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_TIMESTAMP; +import static org.assertj.core.test.ExpectedException.none; + +import java.sql.Timestamp; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.assertj.core.api.AbstractIterableAssert; +import org.assertj.core.api.AbstractListAssert; +import org.assertj.core.test.ExpectedException; +import org.assertj.core.test.FluentJedi; +import org.assertj.core.test.Name; +import org.assertj.core.util.CaseInsensitiveStringComparator; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +/** + * Tests for: + *
      + *
    • {@link AbstractIterableAssert#extractingResultOf(String)}, + *
    • {@link AbstractIterableAssert#extractingResultOf(String, Class)}. + *
    + * + * @author MichaƂ Piotrkowski + */ +public class IterableAssert_extractingResultOf_with_SortedSet_Test { + + private static FluentJedi yoda; + private static FluentJedi vader; + private static Iterable jedis; + + @BeforeClass + public static void setUpOnce() { + yoda = new FluentJedi(new Name("Yoda"), 800, false); + vader = new FluentJedi(new Name("Darth Vader"), 50, true); + jedis = newSortedSet(yoda, vader); + } + + @Rule + public ExpectedException thrown = none(); + + @Test + public void should_allow_assertions_on_method_invocation_result_extracted_from_given_iterable() { + // extract method result + assertThat(jedis).extractingResultOf("age").containsOnly(800, 50); + // extract if method result is primitive + assertThat(jedis).extractingResultOf("darkSide").containsOnly(false, true); + // extract if method result is also a property + assertThat(jedis).extractingResultOf("name").containsOnly(new Name("Yoda"), new Name("Darth Vader")); + // extract toString method result + assertThat(jedis).extractingResultOf("toString").containsOnly("Yoda", "Darth Vader"); + } + + @Test + public void should_allow_assertions_on_method_invocation_result_extracted_from_given_iterable_with_enforcing_return_type() { + assertThat(jedis).extractingResultOf("name", Name.class).containsOnly(new Name("Yoda"), new Name("Darth Vader")); + } + + @Test + public void should_throw_error_if_no_method_with_given_name_can_be_extracted() { + thrown.expectIllegalArgumentException("Can't find method 'unknown' in class FluentJedi.class. Make sure public method exists and accepts no arguments!"); + assertThat(jedis).extractingResultOf("unknown"); + } + + @Test + public void should_use_method_name_as_description_when_extracting_result_of_method_list() { + thrown.expectAssertionErrorWithMessageContaining("[Extracted: result of age()]"); + + assertThat(jedis).extractingResultOf("age").isEmpty(); + } + + @Test + public void should_use_method_name_as_description_when_extracting_typed_result_of_method_list() { + thrown.expectAssertionErrorWithMessageContaining("[Extracted: result of age()]"); + + assertThat(jedis).extractingResultOf("age", Integer.class).isEmpty(); + } + + @Test + public void extractingResultOf_should_keep_assertion_state() { + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + AbstractListAssert assertion = assertThat(jedis).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, + "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, + Timestamp.class) + .extractingResultOf("toString") + .usingComparatorForType(CaseInsensitiveStringComparator.instance, + String.class) + .containsOnly("YODA", "darth vader"); + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(String.class)).isSameAs(CaseInsensitiveStringComparator.instance); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + @Test + public void strongly_typed_extractingResultOf_should_keep_assertion_state() { + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + AbstractListAssert assertion = assertThat(jedis).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, + "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, + Timestamp.class) + .extractingResultOf("toString", String.class) + .usingComparatorForType(CaseInsensitiveStringComparator.instance, + String.class) + .containsOnly("YODA", "darth vader"); + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(String.class)).isSameAs(CaseInsensitiveStringComparator.instance); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + private static SortedSet newSortedSet(FluentJedi... jedis) { + TreeSet jediSortedSet = new TreeSet<>(comparing(FluentJedi::age)); + for (FluentJedi cartoonCharacter : jedis) { + jediSortedSet.add(cartoonCharacter); + } + return jediSortedSet; + } + +} diff --git a/src/test/java/org/assertj/core/api/iterable/IterableAssert_extracting_with_SortedSet_Test.java b/src/test/java/org/assertj/core/api/iterable/IterableAssert_extracting_with_SortedSet_Test.java new file mode 100644 index 0000000000..187c709833 --- /dev/null +++ b/src/test/java/org/assertj/core/api/iterable/IterableAssert_extracting_with_SortedSet_Test.java @@ -0,0 +1,620 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * Copyright 2012-2018 the original author or authors. + */ +package org.assertj.core.api.iterable; + +import static java.util.Comparator.comparing; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.Assertions.setRemoveAssertJRelatedElementsFromStackTrace; +import static org.assertj.core.api.Assertions.tuple; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorForElementFieldsWithNamesOf; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorForElementFieldsWithTypeOf; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorsByTypeOf; +import static org.assertj.core.api.GroupAssertTestHelper.firstNameFunction; +import static org.assertj.core.api.GroupAssertTestHelper.lastNameFunction; +import static org.assertj.core.api.GroupAssertTestHelper.throwingFirstNameExtractor; +import static org.assertj.core.data.TolkienCharacter.Race.DWARF; +import static org.assertj.core.data.TolkienCharacter.Race.ELF; +import static org.assertj.core.data.TolkienCharacter.Race.HOBBIT; +import static org.assertj.core.data.TolkienCharacter.Race.MAIA; +import static org.assertj.core.data.TolkienCharacter.Race.MAN; +import static org.assertj.core.extractor.Extractors.byName; +import static org.assertj.core.presentation.UnicodeRepresentation.UNICODE_REPRESENTATION; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_STRING; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_TIMESTAMP; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_TUPLE; +import static org.assertj.core.test.ExpectedException.none; +import static org.assertj.core.util.Lists.list; + +import java.sql.Timestamp; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.assertj.core.api.AbstractListAssert; +import org.assertj.core.data.TolkienCharacter; +import org.assertj.core.extractor.Extractors; +import org.assertj.core.groups.Tuple; +import org.assertj.core.test.Employee; +import org.assertj.core.test.ExpectedException; +import org.assertj.core.test.Name; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class IterableAssert_extracting_with_SortedSet_Test { + + private Employee yoda; + private Employee luke; + private SortedSet jedis; + private SortedSet fellowshipOfTheRing; + + private static final Extractor firstName = new Extractor() { + @Override + public String extract(Employee input) { + return input.getName().getFirst(); + } + }; + + private static final Extractor age = new Extractor() { + @Override + public Integer extract(Employee input) { + return input.getAge(); + } + }; + + private static final ThrowingExtractor throwingExtractor = new ThrowingExtractor() { + @Override + public Object extractThrows(Employee employee) throws Exception { + if (employee.getAge() < 20) throw new Exception("age < 20"); + return employee.getName().getFirst(); + } + }; + + @Before + public void setUp() { + yoda = new Employee(1L, new Name("Yoda"), 800); + luke = new Employee(2L, new Name("Luke", "Skywalker"), 26); + jedis = new TreeSet<>(comparing(Employee::getAge)); + jedis.add(luke); + jedis.add(yoda); + + fellowshipOfTheRing = new TreeSet<>(comparing(TolkienCharacter::getName)); + fellowshipOfTheRing.add(TolkienCharacter.of("Frodo", 33, HOBBIT)); + fellowshipOfTheRing.add(TolkienCharacter.of("Sam", 38, HOBBIT)); + fellowshipOfTheRing.add(TolkienCharacter.of("Gandalf", 2020, MAIA)); + fellowshipOfTheRing.add(TolkienCharacter.of("Legolas", 1000, ELF)); + fellowshipOfTheRing.add(TolkienCharacter.of("Pippin", 28, HOBBIT)); + fellowshipOfTheRing.add(TolkienCharacter.of("Gimli", 139, DWARF)); + fellowshipOfTheRing.add(TolkienCharacter.of("Aragorn", 87, MAN)); + fellowshipOfTheRing.add(TolkienCharacter.of("Boromir", 37, MAN)); + setRemoveAssertJRelatedElementsFromStackTrace(false); + } + + @Rule + public ExpectedException thrown = none(); + + @Test + public void should_allow_assertions_on_property_values_extracted_from_given_iterable() { + assertThat(jedis).extracting("age") + .as("extract property backed by a private field") + .containsOnly(800, 26); + assertThat(jedis).extracting("adult") + .as("extract pure property") + .containsOnly(true, true); + assertThat(jedis).extracting("name.first") + .as("nested property") + .containsOnly("Yoda", "Luke"); + assertThat(jedis).extracting("name") + .as("extract field that is also a property") + .containsOnly(new Name("Yoda"), new Name("Luke", "Skywalker")); + assertThat(jedis).extracting("name", Name.class) + .as("extract field that is also a property but specifying the extracted type") + .containsOnly(new Name("Yoda"), new Name("Luke", "Skywalker")); + } + + @Test + public void should_allow_assertions_on_null_property_values_extracted_from_given_iterable() { + yoda.name.setFirst(null); + assertThat(jedis).extracting("name.first") + .as("not null property but null nested property") + .containsOnly(null, "Luke"); + yoda.setName(null); + assertThat(jedis).extracting("name.first") + .as("extract nested property when top property is null") + .containsOnly(null, "Luke"); + assertThat(jedis).extracting("name") + .as("null property") + .containsOnly(null, new Name("Luke", "Skywalker")); + } + + @Test + public void should_allow_assertions_on_field_values_extracted_from_given_iterable() { + assertThat(jedis).extracting("id") + .as("extract field") + .containsOnly(1L, 2L); + assertThat(jedis).extracting("surname") + .as("null field") + .containsNull(); + assertThat(jedis).extracting("surname.first") + .as("null nested field") + .containsNull(); + yoda.surname = new Name(); + assertThat(jedis).extracting("surname.first") + .as("not null field but null nested field") + .containsNull(); + yoda.surname = new Name("Master"); + assertThat(jedis).extracting("surname.first") + .as("nested field") + .containsOnly("Master", null); + assertThat(jedis).extracting("surname", Name.class) + .as("extract field specifying the extracted type") + .containsOnly(new Name("Master"), null); + } + + @Test + public void should_allow_assertions_on_property_values_extracted_from_given_iterable_with_extracted_type_defined() { + // extract field that is also a property and check generic for comparator. + assertThat(jedis).extracting("name", Name.class) + .usingElementComparator((o1, o2) -> o1.getFirst().compareTo(o2.getFirst())) + .containsOnly(new Name("Yoda"), new Name("Luke", "Skywalker")); + } + + @Test + public void should_throw_error_if_no_property_nor_field_with_given_name_can_be_extracted() { + thrown.expectIntrospectionError(); + assertThat(jedis).extracting("unknown"); + } + + @Test + public void should_allow_assertions_on_multiple_extracted_values_from_given_iterable() { + assertThat(jedis).extracting("name.first", "age", "id").containsOnly(tuple("Yoda", 800, 1L), + tuple("Luke", 26, 2L)); + } + + @Test + public void should_throw_error_if_one_property_or_field_can_not_be_extracted() { + thrown.expectIntrospectionError(); + assertThat(jedis).extracting("unknown", "age", "id") + .containsOnly(tuple("Yoda", 800, 1L), tuple("Luke", 26, 2L)); + } + + @Test + public void should_allow_extracting_single_values_using_extractor() { + assertThat(jedis).extracting(firstName).containsOnly("Yoda", "Luke"); + assertThat(jedis).extracting(age).containsOnly(26, 800); + } + + @Test + public void should_allow_assertions_on_extractor_assertions_extracted_from_given_array_compatibility_runtimeexception() { + thrown.expect(RuntimeException.class); + assertThat(jedis).extracting(new Extractor() { + @Override + public String extract(Employee input) { + if (input.getAge() > 100) { + throw new RuntimeException("age > 100"); + } + return input.getName().getFirst(); + } + }); + } + + @Test + public void should_allow_assertions_on_extractor_assertions_extracted_from_given_array() { + assertThat(jedis).extracting(input -> input.getName().getFirst()).containsOnly("Yoda", "Luke"); + } + + @Test + public void should_rethrow_throwing_extractor_checked_exception_as_a_runtime_exception() { + thrown.expect(RuntimeException.class, "java.lang.Exception: age > 100"); + assertThat(jedis).extracting(employee -> { + if (employee.getAge() > 100) throw new Exception("age > 100"); + return employee.getName().getFirst(); + }); + } + + @Test + public void should_let_throwing_extractor_runtime_exception_bubble_up() { + thrown.expect(RuntimeException.class, "age > 100"); + assertThat(jedis).extracting(employee -> { + if (employee.getAge() > 100) throw new RuntimeException("age > 100"); + return employee.getName().getFirst(); + }); + } + + @Test + public void should_allow_extracting_with_throwing_extractor() { + assertThat(jedis).extracting(employee -> { + if (employee.getAge() < 20) throw new Exception("age < 20"); + return employee.getName().getFirst(); + }).containsOnly("Yoda", "Luke"); + } + + @Test + public void should_allow_extracting_with_anonymous_class_throwing_extractor() { + assertThat(jedis).extracting(new ThrowingExtractor() { + @Override + public Object extractThrows(Employee employee) throws Exception { + if (employee.getAge() < 20) throw new Exception("age < 20"); + return employee.getName().getFirst(); + } + }).containsOnly("Yoda", "Luke"); + } + + @Test + public void should_allow_extracting_multiple_values_using_extractor() { + assertThat(jedis).extracting(new Extractor() { + @Override + public Tuple extract(Employee input) { + return new Tuple(input.getName().getFirst(), input.getAge(), input.id); + } + }).containsOnly(tuple("Yoda", 800, 1L), tuple("Luke", 26, 2L)); + } + + @Test + public void should_allow_extracting_by_toString_method() { + assertThat(jedis).extracting(Extractors.toStringMethod()) + .containsOnly("Employee[id=1, name=Name[first='Yoda', last='null'], age=800]", + "Employee[id=2, name=Name[first='Luke', last='Skywalker'], age=26]"); + } + + @Test + public void should_allow_assertions_by_using_function_extracted_from_given_iterable() throws Exception { + assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName) + .contains("Boromir", "Gandalf", "Frodo") + .doesNotContain("Sauron", "Elrond"); + } + + @Test + public void should_throw_error_if_function_fails() throws Exception { + RuntimeException thrown = new RuntimeException(); + assertThatThrownBy(() -> assertThat(fellowshipOfTheRing).extracting(e -> { + throw thrown; + }).isEqualTo(thrown)); + } + + @Test + public void should_allow_assertions_on_two_extracted_values_from_given_iterable_by_using_a_function() { + + assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName, + TolkienCharacter::getAge) + .containsOnly(tuple("Frodo", 33), + tuple("Sam", 38), + tuple("Gandalf", 2020), + tuple("Legolas", 1000), + tuple("Pippin", 28), + tuple("Gimli", 139), + tuple("Aragorn", 87), + tuple("Boromir", 37)); + } + + @Test + public void should_allow_assertions_on_three_extracted_values_from_given_iterable_by_using_a_function() { + + assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName, + TolkienCharacter::getAge, + TolkienCharacter::getRace) + .containsOnly(tuple("Frodo", 33, TolkienCharacter.Race.HOBBIT), + tuple("Sam", 38, HOBBIT), + tuple("Gandalf", 2020, MAIA), + tuple("Legolas", 1000, ELF), + tuple("Pippin", 28, HOBBIT), + tuple("Gimli", 139, DWARF), + tuple("Aragorn", 87, MAN), + tuple("Boromir", 37, MAN)); + } + + @Test + public void should_allow_assertions_on_four_extracted_values_from_given_iterable_by_using_a_function() { + + assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName, + TolkienCharacter::getAge, + TolkienCharacter::getRace, + character -> character.name) + .containsOnly(tuple("Frodo", 33, HOBBIT, "Frodo"), + tuple("Sam", 38, HOBBIT, "Sam"), + tuple("Gandalf", 2020, MAIA, "Gandalf"), + tuple("Legolas", 1000, ELF, "Legolas"), + tuple("Pippin", 28, HOBBIT, "Pippin"), + tuple("Gimli", 139, DWARF, "Gimli"), + tuple("Aragorn", 87, MAN, "Aragorn"), + tuple("Boromir", 37, MAN, "Boromir")); + } + + @Test + public void should_allow_assertions_on_five_extracted_values_from_given_iterable_by_using_a_function() { + + assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName, + TolkienCharacter::getAge, + TolkienCharacter::getRace, + character -> character.name, + character -> character.age) + .containsOnly(tuple("Frodo", 33, HOBBIT, "Frodo", 33), + tuple("Sam", 38, HOBBIT, "Sam", 38), + tuple("Gandalf", 2020, MAIA, "Gandalf", 2020), + tuple("Legolas", 1000, ELF, "Legolas", 1000), + tuple("Pippin", 28, HOBBIT, "Pippin", 28), + tuple("Gimli", 139, DWARF, "Gimli", 139), + tuple("Aragorn", 87, MAN, "Aragorn", 87), + tuple("Boromir", 37, MAN, "Boromir", 37)); + } + + @Test + public void should_allow_assertions_on_more_than_five_extracted_values_from_given_iterable_by_using_a_function() { + + assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName, + TolkienCharacter::getAge, + TolkienCharacter::getRace, + character -> character.name, + character -> character.age, + character -> character.race) + .containsOnly(tuple("Frodo", 33, HOBBIT, "Frodo", 33, HOBBIT), + tuple("Sam", 38, HOBBIT, "Sam", 38, HOBBIT), + tuple("Gandalf", 2020, MAIA, "Gandalf", 2020, MAIA), + tuple("Legolas", 1000, ELF, "Legolas", 1000, ELF), + tuple("Pippin", 28, HOBBIT, "Pippin", 28, HOBBIT), + tuple("Gimli", 139, DWARF, "Gimli", 139, DWARF), + tuple("Aragorn", 87, MAN, "Aragorn", 87, MAN), + tuple("Boromir", 37, MAN, "Boromir", 37, MAN)); + } + + @Test + public void should_use_property_field_names_as_description_when_extracting_simple_value_list() { + thrown.expectAssertionErrorWithMessageContaining("[Extracted: name.first]"); + + assertThat(jedis).extracting("name.first").isEmpty(); + } + + @Test + public void should_use_property_field_names_as_description_when_extracting_typed_simple_value_list() { + thrown.expectAssertionErrorWithMessageContaining("[Extracted: name.first]"); + + assertThat(jedis).extracting("name.first", String.class).isEmpty(); + } + + @Test + public void should_use_property_field_names_as_description_when_extracting_tuples_list() { + thrown.expectAssertionErrorWithMessageContaining("[Extracted: name.first, name.last]"); + + assertThat(jedis).extracting("name.first", "name.last").isEmpty(); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_typed_simple_value_list() { + thrown.expectAssertionErrorWithMessageContaining("[check employees first name]"); + + assertThat(jedis).as("check employees first name") + .extracting("name.first", String.class) + .isEmpty(); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_tuples_list() { + thrown.expectAssertionErrorWithMessageContaining("[check employees name]"); + + assertThat(jedis).as("check employees name") + .extracting("name.first", "name.last") + .isEmpty(); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_simple_value_list() { + thrown.expectAssertionErrorWithMessageContaining("[check employees first name]"); + + assertThat(jedis).as("check employees first name") + .extracting("name.first") + .isEmpty(); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_using_extractor() { + thrown.expectAssertionErrorWithMessageContaining("[check employees first name]"); + + assertThat(jedis).as("check employees first name").extracting(new Extractor() { + @Override + public String extract(Employee input) { + return input.getName().getFirst(); + } + }).isEmpty(); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_using_throwing_extractor() { + thrown.expectAssertionErrorWithMessageContaining("[expected exception]"); + + assertThat(jedis).as("expected exception") + .extracting(throwingExtractor) + .containsOnly("Luke"); + } + + @Test + public void should_extract_tuples_according_to_given_functions() { + assertThat(jedis).extracting(firstNameFunction, lastNameFunction) + .contains(tuple("Yoda", null), tuple("Luke", "Skywalker")); + } + + @Test + public void extracting_by_several_functions_should_keep_assertion_state() { + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + AbstractListAssert assertion = assertThat(jedis).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, + "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, + Timestamp.class) + .extracting(firstNameFunction, lastNameFunction) + .usingComparatorForType(ALWAY_EQUALS_TUPLE, Tuple.class) + .contains(tuple("YODA", null), tuple("Luke", "Skywalker")); + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(Tuple.class)).isSameAs(ALWAY_EQUALS_TUPLE); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + @Test + public void extracting_by_name_should_keep_assertion_state() { + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + AbstractListAssert assertion = assertThat(jedis).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, + "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, + Timestamp.class) + .extracting("name.first") + .usingComparatorForType(ALWAY_EQUALS_STRING, String.class) + .contains("YODA", "Luke"); + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(String.class)).isSameAs(ALWAY_EQUALS_STRING); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + @Test + public void extracting_by_strongly_typed_name_should_keep_assertion_state() { + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + AbstractListAssert assertion = assertThat(jedis).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, + "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, + Timestamp.class) + .extracting("name.first", String.class) + .usingComparatorForType(ALWAY_EQUALS_STRING, String.class) + .contains("YODA", "Luke"); + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(String.class)).isSameAs(ALWAY_EQUALS_STRING); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + @Test + public void extracting_by_multiple_names_should_keep_assertion_state() { + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + AbstractListAssert assertion = assertThat(jedis).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, + "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, + Timestamp.class) + .extracting("name.first", "name.last") + .usingComparatorForType(ALWAY_EQUALS_TUPLE, Tuple.class) + .contains(tuple("YODA", null), tuple("Luke", "Skywalker")); + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(Tuple.class)).isSameAs(ALWAY_EQUALS_TUPLE); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + @Test + public void extracting_by_single_extractor_should_keep_assertion_state() { + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + AbstractListAssert assertion = assertThat(jedis).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, + "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, + Timestamp.class) + .extracting(byName("name.first")) + .usingComparatorForType(ALWAY_EQUALS_STRING, String.class) + .contains("YODA", "Luke"); + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(String.class)).isSameAs(ALWAY_EQUALS_STRING); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + @Test + public void extracting_by_throwing_extractor_should_keep_assertion_state() { + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + AbstractListAssert assertion = assertThat(jedis).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, + "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, + Timestamp.class) + .extracting(throwingFirstNameExtractor) + .usingComparatorForType(ALWAY_EQUALS_STRING, String.class) + .contains("YODA", "Luke"); + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(String.class)).isSameAs(ALWAY_EQUALS_STRING); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + @Test + public void extracting_should_not_propagate_the_sorted_set_element_comparator() { + // GIVEN + SortedSet sortedSet = new TreeSet<>(comparing(Data::getValue)); + sortedSet.add(new Data("1")); + sortedSet.add(new Data("2")); + sortedSet.add(new Data("3")); + // THEN + assertThat(sortedSet).extracting(Data::getValue) + .containsOnly("1", "2", "3"); + } + + @Test + public void extracting_should_fail_it_the_propagated_element_comparator_is_incompatible() { + // GIVEN + Iterable list = list(new Data("1"), new Data("2"), new Data("3")); + // WHEN + Throwable error = catchThrowable(() -> assertThat(list).usingElementComparator(comparing(Data::getValue)) + .extracting(Data::getValue) + .containsOnly("1", "2", "3")); + // THEN + assertThat(error).isInstanceOf(ClassCastException.class); + } + + private static class Data { + private final String value; + + private Data(String value) { + this.value = value; + } + + private String getValue() { + return value; + } + } + +} diff --git a/src/test/java/org/assertj/core/api/iterable/IterableAssert_flatExtracting_with_SortedSet_Test.java b/src/test/java/org/assertj/core/api/iterable/IterableAssert_flatExtracting_with_SortedSet_Test.java new file mode 100644 index 0000000000..f3bebbf03d --- /dev/null +++ b/src/test/java/org/assertj/core/api/iterable/IterableAssert_flatExtracting_with_SortedSet_Test.java @@ -0,0 +1,250 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * Copyright 2012-2018 the original author or authors. + */ +package org.assertj.core.api.iterable; + +import static java.util.Comparator.comparing; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorForElementFieldsWithNamesOf; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorForElementFieldsWithTypeOf; +import static org.assertj.core.api.GroupAssertTestHelper.comparatorsByTypeOf; +import static org.assertj.core.presentation.UnicodeRepresentation.UNICODE_REPRESENTATION; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_STRING; +import static org.assertj.core.test.AlwaysEqualComparator.ALWAY_EQUALS_TIMESTAMP; +import static org.assertj.core.test.AlwaysEqualComparator.alwaysEqual; + +import java.sql.Timestamp; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.assertj.core.api.AbstractIterableAssert; +import org.assertj.core.api.AbstractListAssert; +import org.assertj.core.test.AlwaysEqualComparator; +import org.assertj.core.test.CartoonCharacter; +import org.assertj.core.test.ExpectedException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * Tests for {@link AbstractIterableAssert#flatExtracting(Extractor)} + * + * @author Mateusz Haligowski + */ +public class IterableAssert_flatExtracting_with_SortedSet_Test { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private CartoonCharacter bart; + private CartoonCharacter lisa; + private CartoonCharacter maggie; + private CartoonCharacter homer; + private CartoonCharacter pebbles; + private CartoonCharacter fred; + + private static final ThrowingExtractor, Exception> childrenThrowingExtractor = CartoonCharacter::getChildren; + + private static final Extractor> children = new Extractor>() { + @Override + public List extract(CartoonCharacter input) { + return input.getChildren(); + } + }; + + private final ThrowingExtractor, Exception> throwingExtractor = new ThrowingExtractor, Exception>() { + @Override + public List extractThrows(CartoonCharacter cartoonCharacter) throws Exception { + if (cartoonCharacter.getChildren().isEmpty()) throw new Exception("no children"); + return cartoonCharacter.getChildren(); + } + }; + + @Before + public void setUp() { + bart = new CartoonCharacter("Bart Simpson"); + lisa = new CartoonCharacter("Lisa Simpson"); + maggie = new CartoonCharacter("Maggie Simpson"); + + homer = new CartoonCharacter("Homer Simpson"); + homer.addChildren(bart, lisa, maggie); + + pebbles = new CartoonCharacter("Pebbles Flintstone"); + fred = new CartoonCharacter("Fred Flintstone"); + fred.addChildren(pebbles); + } + + @Test + public void should_allow_assertions_on_joined_lists_when_extracting_children() { + assertThat(newSortedSet(homer, fred)).flatExtracting(children) + .containsOnly(bart, lisa, maggie, pebbles); + } + + @Test + public void should_allow_assertions_on_empty_result_lists() { + assertThat(newSortedSet(bart, lisa, maggie)).flatExtracting(children) + .isEmpty(); + } + + @Test + public void should_throw_null_pointer_exception_when_extracting_from_null() { + thrown.expectNullPointerException(); + assertThat(newSortedSet(homer, null)).flatExtracting(children); + } + + @Test + public void should_rethrow_throwing_extractor_checked_exception_as_a_runtime_exception() { + SortedSet childCharacters = newSortedSet(bart, lisa, maggie); + thrown.expect(RuntimeException.class, "java.lang.Exception: no children"); + assertThat(childCharacters).flatExtracting(cartoonCharacter -> { + if (cartoonCharacter.getChildren().isEmpty()) throw new Exception("no children"); + return cartoonCharacter.getChildren(); + }); + } + + @Test + public void should_let_throwing_extractor_runtime_exception_bubble_up() { + SortedSet childCharacters = newSortedSet(bart, lisa, maggie); + thrown.expect(RuntimeException.class, "no children"); + assertThat(childCharacters).flatExtracting(cartoonCharacter -> { + if (cartoonCharacter.getChildren().isEmpty()) throw new RuntimeException("no children"); + return cartoonCharacter.getChildren(); + }); + } + + @Test + public void should_allow_assertions_on_joined_lists_when_extracting_children_with_throwing_extractor() { + SortedSet cartoonCharacters = newSortedSet(homer, fred); + assertThat(cartoonCharacters).flatExtracting(cartoonCharacter -> { + if (cartoonCharacter.getChildren().isEmpty()) throw new Exception("no children"); + return cartoonCharacter.getChildren(); + }).containsOnly(bart, lisa, maggie, pebbles); + } + + @Test + public void should_allow_assertions_on_joined_lists_when_extracting_children_with_anonymous_class_throwing_extractor() { + SortedSet cartoonCharacters = newSortedSet(homer, fred); + assertThat(cartoonCharacters).flatExtracting(new ThrowingExtractor, Exception>() { + @Override + public List extractThrows(CartoonCharacter cartoonCharacter) throws Exception { + if (cartoonCharacter.getChildren().isEmpty()) throw new Exception("no children"); + return cartoonCharacter.getChildren(); + } + }).containsOnly(bart, lisa, maggie, pebbles); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_using_extractor() { + thrown.expectAssertionErrorWithMessageContaining("[expected description]"); + + assertThat(newSortedSet(homer)).as("expected description") + .flatExtracting(children) + .isEmpty(); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_using_single_field_name() { + thrown.expectAssertionErrorWithMessageContaining("[expected description]"); + + assertThat(newSortedSet(homer)).as("expected description") + .flatExtracting("children") + .isEmpty(); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_using_multiple_field_names() { + thrown.expectAssertionErrorWithMessageContaining("[expected description]"); + + assertThat(newSortedSet(homer)).as("expected description") + .flatExtracting("children", "name") + .isEmpty(); + } + + public void should_keep_existing_description_if_set_when_extracting_using_multiple_extractors_varargs() { + thrown.expectAssertionErrorWithMessageContaining("[expected description]"); + + assertThat(newSortedSet(homer)).as("expected description") + .flatExtracting(children, children) + .isEmpty(); + } + + @Test + public void should_keep_existing_description_if_set_when_extracting_using_multiple_throwing_extractors_varargs() { + thrown.expectAssertionErrorWithMessageContaining("[expected description]"); + + assertThat(newSortedSet(homer)).as("expected description") + .flatExtracting(throwingExtractor, throwingExtractor) + .isEmpty(); + } + + @Test + public void flatExtracting_should_keep_assertion_state() { + // GIVEN + AlwaysEqualComparator cartoonCharacterAlwaysEqualComparator = alwaysEqual(); + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + // @format:off + AbstractListAssert assertion + = assertThat(newSortedSet(homer, fred)).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, Timestamp.class) + .flatExtracting(children) + .usingComparatorForType(cartoonCharacterAlwaysEqualComparator, CartoonCharacter.class) + .contains(bart, lisa, new CartoonCharacter("Unknown")); + // @format:on + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(CartoonCharacter.class)).isSameAs(cartoonCharacterAlwaysEqualComparator); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + @Test + public void flatExtracting_with_ThrowingExtractor_should_keep_assertion_state() { + // GIVEN + AlwaysEqualComparator cartoonCharacterAlwaysEqualComparator = alwaysEqual(); + // WHEN + // not all comparators are used but we want to test that they are passed correctly after extracting + // @format:off + AbstractListAssert assertion + = assertThat(newSortedSet(homer, fred)).as("test description") + .withFailMessage("error message") + .withRepresentation(UNICODE_REPRESENTATION) + .usingComparatorForElementFieldsWithNames(ALWAY_EQUALS_STRING, "foo") + .usingComparatorForElementFieldsWithType(ALWAY_EQUALS_TIMESTAMP, Timestamp.class) + .flatExtracting(childrenThrowingExtractor) + .usingComparatorForType(cartoonCharacterAlwaysEqualComparator, CartoonCharacter.class) + .contains(bart, lisa, new CartoonCharacter("Unknown")); + // @format:on + // THEN + assertThat(assertion.descriptionText()).isEqualTo("test description"); + assertThat(assertion.info.representation()).isEqualTo(UNICODE_REPRESENTATION); + assertThat(assertion.info.overridingErrorMessage()).isEqualTo("error message"); + assertThat(comparatorsByTypeOf(assertion).get(CartoonCharacter.class)).isSameAs(cartoonCharacterAlwaysEqualComparator); + assertThat(comparatorForElementFieldsWithTypeOf(assertion).get(Timestamp.class)).isSameAs(ALWAY_EQUALS_TIMESTAMP); + assertThat(comparatorForElementFieldsWithNamesOf(assertion).get("foo")).isSameAs(ALWAY_EQUALS_STRING); + } + + private static SortedSet newSortedSet(CartoonCharacter... cartoonCharacters) { + TreeSet cartoonCharacterSortedSet = new TreeSet<>(comparing(CartoonCharacter::getName)); + for (CartoonCharacter cartoonCharacter : cartoonCharacters) { + cartoonCharacterSortedSet.add(cartoonCharacter); + } + return cartoonCharacterSortedSet; + } + +}