Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2540,8 +2540,8 @@ integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts.

In addition to implicit conversion from strings to the target types listed in the above
table, JUnit Jupiter also provides a fallback mechanism for automatic conversion from a
`String` to a given target type if the target type declares exactly one suitable _factory
method_ or a _factory constructor_ as defined below.
`String` to a given target type if the target type declares a suitable _factory method_
or _factory constructor_ as defined below.

- __factory method__: a non-private, `static` method declared in the target type that
accepts either a single `String` argument or a single `CharSequence` argument and
Expand All @@ -2551,9 +2551,19 @@ method_ or a _factory constructor_ as defined below.
either a single `String` argument or a single `CharSequence` argument. Note that the
target type must be declared as either a top-level class or as a `static` nested class.

NOTE: If multiple _factory methods_ are discovered, they will be ignored. If a _factory
method_ and a _factory constructor_ are discovered, the factory method will be used
instead of the constructor.
If there are multiple _factory methods_ or _factory constructors_, matching proceeds in the
following order:

1. A single _factory method_ accepting a `String` argument.
2. A single _factory constructor_ accepting a `String` argument.
3. A single _factory method_ accepting a `CharSequence` argument.
4. A single _factory constructor_ accepting a `CharSequence` argument.
5. A single _factory method_ accepting a `String` argument once all `@Deprecated` factory
methods have been removed from the set of methods being considered.
6. A single _factory method_ accepting a `CharSequence` argument once all `@Deprecated` factory
methods have been removed from the set of methods being considered.

NOTE: If multiple _factory methods_ are discovered, no match occurs.

For example, in the following `@ParameterizedTest` method, the `Book` argument will be
created by invoking the `Book.fromTitle(String)` factory method and passing `"42 Cats"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
/**
* {@code FallbackStringToObjectConverter} is a {@link StringToObjectConverter}
* that provides a fallback conversion strategy for converting from a
* {@link String} to a given target type by invoking a static factory method
* or factory constructor defined in the target type.
* {@link String} or {@link CharSequence} to a given target type by invoking a
* static factory method or factory constructor defined in the target type.
*
* <h2>Search Algorithm</h2>
*
Expand Down Expand Up @@ -101,6 +101,11 @@ public boolean canConvertTo(Class<?> targetType) {
if (factory != null) {
return factory;
}
// Third, exclude deprecated methods
factory = findFactoryExcludeDeprecated(type);
if (factory != null) {
return factory;
}
// Else, nothing found.
return NULL_EXECUTABLE;
});
Expand All @@ -109,7 +114,7 @@ public boolean canConvertTo(Class<?> targetType) {
private static @Nullable Function<String, @Nullable Object> findFactoryExecutable(Class<?> targetType,
Class<?> parameterType) {

Method factoryMethod = findFactoryMethod(targetType, parameterType);
Method factoryMethod = findFactoryMethod(targetType, parameterType, false);
if (factoryMethod != null) {
return source -> invokeMethod(factoryMethod, null, source);
}
Expand All @@ -120,9 +125,22 @@ public boolean canConvertTo(Class<?> targetType) {
return null;
}

private static @Nullable Method findFactoryMethod(Class<?> targetType, Class<?> parameterType) {
List<Method> factoryMethods = findMethods(targetType, new IsFactoryMethod(targetType, parameterType),
BOTTOM_UP);
private static @Nullable Function<String, @Nullable Object> findFactoryExcludeDeprecated(Class<?> targetType) {
Method factoryMethodStr = findFactoryMethod(targetType, String.class, true);
if (factoryMethodStr != null) {
return source -> invokeMethod(factoryMethodStr, null, source);
}
Method factoryMethodChSeq = findFactoryMethod(targetType, CharSequence.class, true);
if (factoryMethodChSeq != null) {
return source -> invokeMethod(factoryMethodChSeq, null, source);
}
return null;
}

private static @Nullable Method findFactoryMethod(Class<?> targetType, Class<?> parameterType,
boolean excludeDeprecated) {
List<Method> factoryMethods = findMethods(targetType,
new IsFactoryMethod(targetType, parameterType, excludeDeprecated), BOTTOM_UP);
if (factoryMethods.size() == 1) {
return factoryMethods.get(0);
}
Expand All @@ -143,7 +161,8 @@ public boolean canConvertTo(Class<?> targetType) {
* {@link #test(Method)} is a non-private static factory method for the
* supplied {@link #targetType} and {@link #parameterType}.
*/
record IsFactoryMethod(Class<?> targetType, Class<?> parameterType) implements Predicate<Method> {
record IsFactoryMethod(Class<?> targetType, Class<?> parameterType, boolean excludeDeprecated)
implements Predicate<Method> {

@Override
public boolean test(Method method) {
Expand All @@ -154,6 +173,9 @@ public boolean test(Method method) {
if (isNotStatic(method)) {
return false;
}
if (excludeDeprecated && method.getAnnotation(Deprecated.class) != null) {
return false;
}
return isFactoryCandidate(method, this.parameterType);
}
}
Expand Down
Loading