javagenericsjunit-jupiterparameterized-unit-testparameterized-tests

How can I get the actual type from a generic type parameter


Say, I have an abstract class for testing.

abstract class BaseEntity<SELF extends BaseEntity<SELF>> {
}

I declared an abstract test class for testing subclasses of the BaseEntity.

abstract BaseEntityTest<ENTITY extends BaseEntity<ENTITY>> {

    @ArgumentsSource(BaseEntityTestArgumentsProvider.class)
    @ParameterizedTest
    void _NotBlank_ToString(final Entity entityInstance) {
    }

    protected final Class<ENTITY> entityClass;
}
public class BaseEntityArgumentsProvider implements ArgumentsProvider {

    @Override
    public Stream<? extends Arguments> provideArguments(final ParameterDeclarations parameters,
                                                        final ExtensionContext context)
            throws Exception {
        final var firstParameterType = parameters.getFirst().map(ParameterDeclaration::getParameterType).orElse(null);
        if (firstParameterType == null) {
            return Stream.empty();
        }
        if (!BaseEntity.class.isAssignableFrom(firstParameterType)) {
            return Stream.empty();
        }
        return BaseEntityTest_Utils.getEntityInstanceStream(
                firstParameterType.asSubclass(BaseEntity.class)
        ).map(Arguments::of);
    }
}

Now the firstParameterType is always BaseEntity.class. How can I get the actual type of the ENTITY within the BaseEntityArgumentsProvider class?


Solution

  • Given you have parent:

    public class BaseEntity<SELF> {
    }
    

    Then you have a child:

    public class ChildEntity extends BaseEntity<ChildEntity> {
    }
    

    Then you can have such abstract test:

    abstract class ParentTest<SELF extends BaseEntity<SELF>> {
      @Test
      void test() {
        assertEquals(ChildEntity.class,
            ((ParameterizedType) getClass().getGenericSuperclass())
            .getActualTypeArguments()[0]);
      }
    }
    

    And its concrete child:

    public class ChildTest extends ParentTest<ChildEntity> {
    }
    

    So the key part here is to use reflection to have a type of a generic:

    ((ParameterizedType) getClass().getGenericSuperclass())
            .getActualTypeArguments()[0]
    

    Explanation:

    1. child gets its superclass: var genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();

    2. child gets the type of the first generic: genericSuperclass.getActualTypeArguments()[0].


    UPD: you can also extract this magic-like reflection logic to an utils class:

    public class GenericsUtils {
      public static Class<?> getGenericType(Object object) {
        return (Class<?>) ((ParameterizedType) object.getClass().getGenericSuperclass())
            .getActualTypeArguments()[0];
      }
    }
    

    So you would have this test body:

    assertEquals(ChildEntity.class, GenericsUtils.getGenericType(this));
    

    Source: