Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ JUnit repository on GitHub.
[[release-notes-5.12.0-M1-junit-jupiter-bug-fixes]]
==== Bug Fixes

* ❓
* Provide _runtime_ enclosing types of `@Nested` test classes and contained test methods
to `DisplayNameGenerator` implementations. Prior to this change, such generators were
only able to access the enclosing class in which `@Nested` was declared, but they could
not access the concrete runtime type of the enclosing instance.

[[release-notes-5.12.0-M1-junit-jupiter-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@

package org.junit.jupiter.api;

import static java.util.Collections.emptyList;
import static org.apiguardian.api.API.Status.DEPRECATED;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.STABLE;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
import static org.junit.platform.commons.support.ModifierSupport.isStatic;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

Expand Down Expand Up @@ -74,27 +78,61 @@ public interface DisplayNameGenerator {
/**
* Generate a display name for the given top-level or {@code static} nested test class.
*
* <p>If it returns {@code null}, the default display name generator will be used instead.
* <p>If this method returns {@code null}, the default display name
* generator will be used instead.
*
* @param testClass the class to generate a name for; never {@code null}
* @return the display name for the class; never blank
*/
String generateDisplayNameForClass(Class<?> testClass);

/**
* Generate a display name for the given {@link Nested @Nested} inner test class.
* Generate a display name for the given {@link Nested @Nested} inner test
* class.
*
* <p>If it returns {@code null}, the default display name generator will be used instead.
* <p>If this method returns {@code null}, the default display name
* generator will be used instead.
*
* @param nestedClass the class to generate a name for; never {@code null}
* @return the display name for the nested class; never blank
* @deprecated in favor of {@link #generateDisplayNameForNestedClass(List, Class)}
*/
String generateDisplayNameForNestedClass(Class<?> nestedClass);
@API(status = DEPRECATED, since = "5.12")
@Deprecated
default String generateDisplayNameForNestedClass(Class<?> nestedClass) {
throw new UnsupportedOperationException(
"Implement generateDisplayNameForNestedClass(List<Class<?>>, Class<?>) instead");
}

/**
* Generate a display name for the given {@link Nested @Nested} inner test
* class.
*
* <p>If this method returns {@code null}, the default display name
* generator will be used instead.
*
* @implNote The classes supplied as {@code enclosingInstanceTypes} may
* differ from the classes returned from invocations of
* {@link Class#getEnclosingClass()} &mdash; for example, when a nested test
* class is inherited from a superclass.
*
* @param enclosingInstanceTypes the runtime types of the enclosing
* instances for the test class, ordered from outermost to innermost,
* excluding {@code nestedClass}; never {@code null}
* @param nestedClass the class to generate a name for; never {@code null}
* @return the display name for the nested class; never blank
* @since 5.12
*/
@API(status = EXPERIMENTAL, since = "5.12")
default String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
return generateDisplayNameForNestedClass(nestedClass);
}

/**
* Generate a display name for the given method.
*
* <p>If it returns {@code null}, the default display name generator will be used instead.
* <p>If this method returns {@code null}, the default display name
* generator will be used instead.
*
* @implNote The class instance supplied as {@code testClass} may differ from
* the class returned by {@code testMethod.getDeclaringClass()} &mdash; for
Expand All @@ -103,8 +141,42 @@ public interface DisplayNameGenerator {
* @param testClass the class the test method is invoked on; never {@code null}
* @param testMethod method to generate a display name for; never {@code null}
* @return the display name for the test; never blank
* @deprecated in favor of {@link #generateDisplayNameForMethod(List, Class, Method)}
*/
String generateDisplayNameForMethod(Class<?> testClass, Method testMethod);
@API(status = DEPRECATED, since = "5.12")
@Deprecated
default String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
throw new UnsupportedOperationException(
"Implement generateDisplayNameForMethod(List<Class<?>>, Class<?>, Method) instead");
}

/**
* Generate a display name for the given method.
*
* <p>If this method returns {@code null}, the default display name
* generator will be used instead.
*
* @implNote The classes supplied as {@code enclosingInstanceTypes} may
* differ from the classes returned from invocations of
* {@link Class#getEnclosingClass()} &mdash; for example, when a nested test
* class is inherited from a superclass. Similarly, the class instance
* supplied as {@code testClass} may differ from the class returned by
* {@code testMethod.getDeclaringClass()} &mdash; for example, when a test
* method is inherited from a superclass.
*
* @param enclosingInstanceTypes the runtime types of the enclosing
* instances for the test class, ordered from outermost to innermost,
* excluding {@code testClass}; never {@code null}
* @param testClass the class the test method is invoked on; never {@code null}
* @param testMethod method to generate a display name for; never {@code null}
* @return the display name for the test; never blank
* @since 5.12
*/
@API(status = EXPERIMENTAL, since = "5.12")
default String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
return generateDisplayNameForMethod(testClass, testMethod);
}

/**
* Generate a string representation of the formal parameters of the supplied
Expand Down Expand Up @@ -142,12 +214,13 @@ public String generateDisplayNameForClass(Class<?> testClass) {
}

@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
return nestedClass.getSimpleName();
}

@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
return testMethod.getName() + parameterTypesAsString(testMethod);
}
}
Expand All @@ -168,7 +241,8 @@ public Simple() {
}

@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
String displayName = testMethod.getName();
if (hasParameters(testMethod)) {
displayName += ' ' + parameterTypesAsString(testMethod);
Expand Down Expand Up @@ -202,13 +276,15 @@ public String generateDisplayNameForClass(Class<?> testClass) {
}

@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
return replaceUnderscores(super.generateDisplayNameForNestedClass(nestedClass));
public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
return replaceUnderscores(super.generateDisplayNameForNestedClass(enclosingInstanceTypes, nestedClass));
}

@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
return replaceUnderscores(super.generateDisplayNameForMethod(testClass, testMethod));
public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
return replaceUnderscores(
super.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod));
}

private static String replaceUnderscores(String name) {
Expand Down Expand Up @@ -243,18 +319,21 @@ public String generateDisplayNameForClass(Class<?> testClass) {
}

@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
return getSentenceBeginning(nestedClass);
public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
return getSentenceBeginning(enclosingInstanceTypes, nestedClass);
}

@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
return getSentenceBeginning(testClass) + getFragmentSeparator(testClass)
+ getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod);
public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod) {
return getSentenceBeginning(enclosingInstanceTypes, testClass) + getFragmentSeparator(testClass)
+ getGeneratorFor(testClass).generateDisplayNameForMethod(enclosingInstanceTypes, testClass,
testMethod);
}

private String getSentenceBeginning(Class<?> testClass) {
Class<?> enclosingClass = testClass.getEnclosingClass();
private String getSentenceBeginning(List<Class<?>> enclosingInstanceTypes, Class<?> testClass) {
Class<?> enclosingClass = enclosingInstanceTypes.isEmpty() ? null
: enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1);
boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass));
Optional<String> displayName = findAnnotation(testClass, DisplayName.class)//
.map(DisplayName::value).map(String::trim);
Expand All @@ -280,10 +359,16 @@ private String getSentenceBeginning(Class<?> testClass) {
.filter(IndicativeSentences.class::equals)//
.isPresent();

String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : "");
List<Class<?>> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList()
: enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1);

String prefix = (buildPrefix
? getSentenceBeginning(remainingEnclosingInstanceTypes, enclosingClass)
+ getFragmentSeparator(testClass)
: "");

return prefix + displayName.orElseGet(
() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass));
return prefix + displayName.orElseGet(() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(
remainingEnclosingInstanceTypes, testClass));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
Expand Down Expand Up @@ -89,27 +90,27 @@ static String determineDisplayName(AnnotatedElement element, Supplier<String> di
return displayNameSupplier.get();
}

static String determineDisplayNameForMethod(Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
static String determineDisplayNameForMethod(Supplier<List<Class<?>>> enclosingInstanceTypes, Class<?> testClass,
Method testMethod, JupiterConfiguration configuration) {
return determineDisplayName(testMethod,
createDisplayNameSupplierForMethod(testClass, testMethod, configuration));
createDisplayNameSupplierForMethod(enclosingInstanceTypes, testClass, testMethod, configuration));
}

static Supplier<String> createDisplayNameSupplierForClass(Class<?> testClass, JupiterConfiguration configuration) {
return createDisplayNameSupplier(testClass, configuration,
generator -> generator.generateDisplayNameForClass(testClass));
}

static Supplier<String> createDisplayNameSupplierForNestedClass(Class<?> testClass,
JupiterConfiguration configuration) {
static Supplier<String> createDisplayNameSupplierForNestedClass(Supplier<List<Class<?>>> enclosingInstanceTypes,
Class<?> testClass, JupiterConfiguration configuration) {
return createDisplayNameSupplier(testClass, configuration,
generator -> generator.generateDisplayNameForNestedClass(testClass));
generator -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes.get(), testClass));
}

private static Supplier<String> createDisplayNameSupplierForMethod(Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
private static Supplier<String> createDisplayNameSupplierForMethod(Supplier<List<Class<?>>> enclosingInstanceTypes,
Class<?> testClass, Method testMethod, JupiterConfiguration configuration) {
return createDisplayNameSupplier(testClass, configuration,
generator -> generator.generateDisplayNameForMethod(testClass, testMethod));
generator -> generator.generateDisplayNameForMethod(enclosingInstanceTypes.get(), testClass, testMethod));
}

private static Supplier<String> createDisplayNameSupplier(Class<?> testClass, JupiterConfiguration configuration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand Down Expand Up @@ -59,9 +60,9 @@ public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor im
private final Set<TestTag> tags;

MethodBasedTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
this(uniqueId, determineDisplayNameForMethod(testClass, testMethod, configuration), testClass, testMethod,
configuration);
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
this(uniqueId, determineDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod, configuration),
testClass, testMethod, configuration);
}

MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class<?> testClass, Method testMethod,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.TestInstances;
Expand Down Expand Up @@ -46,8 +47,10 @@ public class NestedClassTestDescriptor extends ClassBasedTestDescriptor {

public static final String SEGMENT_TYPE = "nested-class";

public NestedClassTestDescriptor(UniqueId uniqueId, Class<?> testClass, JupiterConfiguration configuration) {
super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration);
public NestedClassTestDescriptor(UniqueId uniqueId, Class<?> testClass,
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
super(uniqueId, testClass,
createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration);
}

// --- TestDescriptor ------------------------------------------------------
Expand All @@ -62,7 +65,11 @@ public final Set<TestTag> getTags() {

@Override
public List<Class<?>> getEnclosingTestClasses() {
TestDescriptor parent = getParent().orElse(null);
return getEnclosingTestClasses(getParent().orElse(null));
}

@API(status = INTERNAL, since = "5.12")
public static List<Class<?>> getEnclosingTestClasses(TestDescriptor parent) {
if (parent instanceof ClassBasedTestDescriptor) {
ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent;
List<Class<?>> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
Expand Down Expand Up @@ -63,8 +64,8 @@ public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implemen
private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();

public TestFactoryTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, configuration);
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration);
}

// --- Filterable ----------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder;

import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Supplier;

import org.apiguardian.api.API;
import org.junit.jupiter.api.TestInstance.Lifecycle;
Expand Down Expand Up @@ -75,8 +77,8 @@ public class TestMethodTestDescriptor extends MethodBasedTestDescriptor {
private final ReflectiveInterceptorCall<Method, Void> interceptorCall;

public TestMethodTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, configuration);
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration);
this.interceptorCall = defaultInterceptorCall;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.apiguardian.api.API;
Expand Down Expand Up @@ -46,8 +47,8 @@ public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implem
private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();

public TestTemplateTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method templateMethod,
JupiterConfiguration configuration) {
super(uniqueId, testClass, templateMethod, configuration);
Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration);
}

// --- Filterable ----------------------------------------------------------
Expand Down
Loading
Loading