Skip to content

Commit

Permalink
Add InstanceOfAssertFactory for Set instances (#3325)
Browse files Browse the repository at this point in the history
Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com>
  • Loading branch information
scordio and sbrannen committed Feb 25, 2024
1 parent 3211bc6 commit d1d9bc0
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
Expand Down Expand Up @@ -908,6 +909,32 @@ static <ELEMENT> InstanceOfAssertFactory<List, ListAssert<ELEMENT>> list(Class<E
return new InstanceOfAssertFactory<>(List.class, Assertions::<ELEMENT> assertThat);
}

/**
* {@link InstanceOfAssertFactory} for a {@link Set}, assuming {@code Object} as element type.
*
* @see #set(Class)
* @since 3.26.0
*/
@SuppressWarnings("rawtypes") // rawtypes: using Class instance
InstanceOfAssertFactory<Set, AbstractCollectionAssert<?, Collection<?>, Object, ObjectAssert<Object>>> SET = set(Object.class);

/**
* {@link InstanceOfAssertFactory} for a {@link Set}.
*
* @param <E> the {@code Set} element type.
* @param elementType the element type instance.
* @return the factory instance.
*
* @see #SET
* @since 3.26.0
*/
@SuppressWarnings({ "rawtypes", "unused", "unchecked", "RedundantSuppression" })
// rawtypes+unchecked: using Class instance, unused: parameter needed for type inference.
// IntelliJ can warn that this is redundant when it is not.
static <E> InstanceOfAssertFactory<Set, AbstractCollectionAssert<?, Collection<? extends E>, E, ObjectAssert<E>>> set(Class<E> elementType) {
return new InstanceOfAssertFactory<>(Set.class, Assertions::<E> assertThat);
}

/**
* {@link InstanceOfAssertFactory} for a {@link Stream}, assuming {@code Object} as element type.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.assertj.core.api;

import static java.util.stream.Collectors.toMap;
import static org.assertj.core.api.Assertions.as;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.from;
import static org.assertj.core.api.BDDAssertions.then;
Expand All @@ -27,26 +28,32 @@
import java.lang.reflect.TypeVariable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;

class Assertions_sync_with_InstanceOfAssertFactories_Test extends BaseAssertionsTest {

private static final Class<?>[] FIELD_FACTORIES_IGNORED_TYPES = {
// There can be no Comparable field factory with a base type.
private static final Class<?>[] IGNORED_INPUT_TYPES = {
// There is no dedicated `assertThat`.
Set.class
};

private static final Class<?>[] IGNORED_ASSERT_TYPES_FOR_FIELD_FACTORIES = {
// There cannot be a `Comparable` field factory with a base type.
AbstractComparableAssert.class,
// The comparison of the input GenericArrayTypes will always fail, since it verifies the inner TypeVariable which
// returns the defining Method as result of TypeVariable#getGenericDeclaration().
// The comparison of the input `GenericArrayTypes` will always fail, since it verifies the inner `TypeVariable`
// which returns the defining `Method` as a result of `TypeVariable#getGenericDeclaration()`.
ObjectArrayAssert.class, Object2DArrayAssert.class,
// A field factory for an object is pointless.
ObjectAssert.class,
};

private static final Class<?>[] METHOD_FACTORIES_IGNORED_TYPES = {
// The comparison of the input GenericArrayTypes will always fail, since it verifies the inner TypeVariable which
// returns the defining Method as result of TypeVariable#getGenericDeclaration().
private static final Class<?>[] IGNORED_ASSERT_TYPES_FOR_METHOD_FACTORIES = {
// The comparison of the input `GenericArrayTypes` will always fail, since it verifies the inner `TypeVariable`
// which returns the defining `Method` as a result of `TypeVariable#getGenericDeclaration()`.
ObjectArrayAssert.class, Object2DArrayAssert.class,
};

Expand All @@ -73,7 +80,8 @@ void each_standard_assertion_with_type_parameters_should_have_an_instance_of_ass
}

private Map<Type, Type> findAssertThatParameterAndReturnTypes() {
return Stream.of(findMethodsWithName(Assertions.class, "assertThat", ignoredReturnTypes(FIELD_FACTORIES_IGNORED_TYPES)))
return Stream.of(findMethodsWithName(Assertions.class, "assertThat",
ignoredReturnTypes(IGNORED_ASSERT_TYPES_FOR_FIELD_FACTORIES)))
.map(this::toParameterAndReturnTypeEntry)
.filter(not(this::isPrimitiveTypeKey))
.collect(toMap(Entry::getKey, Entry::getValue));
Expand All @@ -87,7 +95,8 @@ private <K, V> boolean isPrimitiveTypeKey(Entry<K, V> entry) {
}

private Map<Type, Type> findTypedAssertThatParameterAndReturnTypes() {
return Stream.of(findMethodsWithName(Assertions.class, "assertThat", ignoredReturnTypes(METHOD_FACTORIES_IGNORED_TYPES)))
return Stream.of(findMethodsWithName(Assertions.class, "assertThat",
ignoredReturnTypes(IGNORED_ASSERT_TYPES_FOR_METHOD_FACTORIES)))
.filter(this::hasTypeParameters)
.map(this::toParameterAndReturnTypeEntry)
.collect(toMap(Entry::getKey, Entry::getValue));
Expand Down Expand Up @@ -118,35 +127,40 @@ private Map<Type, Type> findFieldFactoryTypes() {
.filter(not(Field::isSynthetic)) // Exclude $jacocoData - see #590 and jacoco/jacoco#168
.map(Field::getGenericType)
.map(this::extractTypeParameters)
.filter(not(this::isIgnoredInputType))
.filter(not(this::isIgnoredFieldFactory))
.collect(toMap(Entry::getKey, Entry::getValue));
}

private boolean isIgnoredFieldFactory(Entry<Type, Type> e) {
return isIgnoredFactory(e, FIELD_FACTORIES_IGNORED_TYPES);
return isIgnoredFactory(e, IGNORED_ASSERT_TYPES_FOR_FIELD_FACTORIES);
}

private Map<Type, Type> findMethodFactoryTypes() {
return Stream.of(InstanceOfAssertFactories.class.getMethods())
.map(Method::getGenericReturnType)
.map(this::extractTypeParameters)
.filter(not(this::isIgnoredInputType))
.filter(not(this::isIgnoredMethodFactory))
.collect(toMap(Entry::getKey, Entry::getValue));
}

private boolean isIgnoredMethodFactory(Entry<Type, Type> e) {
return isIgnoredFactory(e, METHOD_FACTORIES_IGNORED_TYPES);
return isIgnoredFactory(e, IGNORED_ASSERT_TYPES_FOR_METHOD_FACTORIES);
}

private boolean isIgnoredFactory(Entry<Type, Type> e, Class<?>... ignoredTypes) {
private boolean isIgnoredFactory(Entry<Type, Type> e, Class<?>[] ignoredTypes) {
return Stream.of(ignoredTypes).anyMatch(type -> e.getValue().equals(type));
}

private boolean isIgnoredInputType(Entry<Type, Type> e) {
return Stream.of(IGNORED_INPUT_TYPES).anyMatch(type -> e.getKey().equals(type));
}

private Entry<Type, Type> extractTypeParameters(Type type) {
assertThat(type).asInstanceOf(type(ParameterizedType.class))
.returns(InstanceOfAssertFactory.class, from(ParameterizedType::getRawType))
.extracting(ParameterizedType::getActualTypeArguments)
.asInstanceOf(ARRAY)
.extracting(ParameterizedType::getActualTypeArguments, as(ARRAY))
.hasSize(2);
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
return entry(normalize(typeArguments[0]), normalize(typeArguments[1]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
import static org.assertj.core.api.InstanceOfAssertFactories.PATH;
import static org.assertj.core.api.InstanceOfAssertFactories.PERIOD;
import static org.assertj.core.api.InstanceOfAssertFactories.PREDICATE;
import static org.assertj.core.api.InstanceOfAssertFactories.SET;
import static org.assertj.core.api.InstanceOfAssertFactories.SHORT;
import static org.assertj.core.api.InstanceOfAssertFactories.SHORT_2D_ARRAY;
import static org.assertj.core.api.InstanceOfAssertFactories.SHORT_ARRAY;
Expand Down Expand Up @@ -119,6 +120,7 @@
import static org.assertj.core.api.InstanceOfAssertFactories.map;
import static org.assertj.core.api.InstanceOfAssertFactories.optional;
import static org.assertj.core.api.InstanceOfAssertFactories.predicate;
import static org.assertj.core.api.InstanceOfAssertFactories.set;
import static org.assertj.core.api.InstanceOfAssertFactories.stream;
import static org.assertj.core.api.InstanceOfAssertFactories.throwable;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
Expand Down Expand Up @@ -176,6 +178,7 @@
import java.util.stream.Stream;

import org.assertj.core.util.Lists;
import org.assertj.core.util.Sets;
import org.assertj.core.util.Strings;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -1135,6 +1138,26 @@ void collection_typed_factory_should_allow_collection_typed_assertions() {
result.contains("Bart", "Lisa");
}

@Test
void set_factory_should_allow_collection_assertions() {
// GIVEN
Object value = Sets.set("Homer", "Marge", "Bart", "Lisa", "Maggie");
// WHEN
AbstractCollectionAssert<?, Collection<?>, Object, ObjectAssert<Object>> result = assertThat(value).asInstanceOf(SET);
// THEN
result.contains("Bart", "Lisa");
}

@Test
void set_typed_factory_should_allow_collection_typed_assertions() {
// GIVEN
Object value = Sets.set("Homer", "Marge", "Bart", "Lisa", "Maggie");
// WHEN
AbstractCollectionAssert<?, Collection<? extends String>, String, ObjectAssert<String>> result = assertThat(value).asInstanceOf(set(String.class));
// THEN
result.contains("Bart", "Lisa");
}

@Test
void list_factory_should_allow_list_assertions() {
// GIVEN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import static org.assertj.core.api.InstanceOfAssertFactories.PATH;
import static org.assertj.core.api.InstanceOfAssertFactories.PERIOD;
import static org.assertj.core.api.InstanceOfAssertFactories.PREDICATE;
import static org.assertj.core.api.InstanceOfAssertFactories.SET;
import static org.assertj.core.api.InstanceOfAssertFactories.SHORT;
import static org.assertj.core.api.InstanceOfAssertFactories.SHORT_2D_ARRAY;
import static org.assertj.core.api.InstanceOfAssertFactories.SHORT_ARRAY;
Expand All @@ -99,6 +100,7 @@
import static org.assertj.core.util.Lists.list;
import static org.assertj.core.util.Maps.newHashMap;
import static org.assertj.core.util.Sets.newHashSet;
import static org.assertj.core.util.Sets.set;
import static org.junit.jupiter.params.provider.Arguments.arguments;

import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -261,6 +263,7 @@ public static Stream<Arguments> should_work_with_any_InstanceOfFactory_source()
arguments(OptionalLong.empty(), OPTIONAL_LONG),
arguments(Paths.get("."), PATH),
arguments((Predicate<String>) String::isEmpty, PREDICATE),
arguments(set("foo"), SET),
arguments(Short.MIN_VALUE, SHORT),
arguments(new short[0], SHORT_ARRAY),
arguments(new short[0][0], SHORT_2D_ARRAY),
Expand Down

0 comments on commit d1d9bc0

Please sign in to comment.