Skip to content

Introduce ConversionService in junit-platform-commons #4219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -40,7 +40,7 @@
* {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency},
* {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc.
*
* <p>If the source and target types are identical the source object will not
* <p>If the source and target types are identical, the source object will not
* be modified.
*
* @since 5.0
@@ -83,21 +83,15 @@ public final Object convert(Object source, Class<?> targetType, ClassLoader clas
return source;
}

if (source instanceof String) {
try {
return convert((String) source, targetType, classLoader);
}
catch (ConversionException ex) {
throw new ArgumentConversionException(ex.getMessage(), ex);
}
try {
return delegateConversion(source, targetType, classLoader);
}
catch (ConversionException ex) {
throw new ArgumentConversionException(ex.getMessage(), ex);
}

throw new ArgumentConversionException(
String.format("No built-in converter for source type %s and target type %s",
source.getClass().getTypeName(), targetType.getTypeName()));
}

Object convert(String source, Class<?> targetType, ClassLoader classLoader) {
Object delegateConversion(Object source, Class<?> targetType, ClassLoader classLoader) {
return ConversionSupport.convert(source, targetType, classLoader);
}

Original file line number Diff line number Diff line change
@@ -47,6 +47,9 @@ protected TypedArgumentConverter(Class<S> sourceType, Class<T> targetType) {
this.targetType = Preconditions.notNull(targetType, "targetType must not be null");
}

/**
* {@inheritDoc}
*/
@Override
public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException {
return convert(source, context.getParameter().getType());
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2015-2025 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.commons.support.conversion;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import org.apiguardian.api.API;

/**
* {@code ConversionService} is an abstraction that allows an input object to
* be converted to an instance of a different class.
*
* <p>Implementations are loaded via the {@link java.util.ServiceLoader} and must
* follow the service provider requirements. They should not make any assumptions
* regarding when they are instantiated or how often they are called. Since
* instances may potentially be cached and called from different threads, they
* should be thread-safe.
*
* <p>Extend {@link TypedConversionService} if your implementation always converts
* from a given source type into a given target type and does not need access to
* the {@link ClassLoader} to perform the conversion.
*
* @since 1.13
* @see ConversionSupport
* @see TypedConversionService
*/
@API(status = EXPERIMENTAL, since = "1.13")
public interface ConversionService {

/**
* Determine if the supplied source object can be converted into an instance
* of the specified target type.
*
* @param source the source object to convert; may be {@code null} but only
* if the target type is a reference type
* @param targetType the target type the source should be converted into;
* never {@code null}
* @param classLoader the {@code ClassLoader} to use; never {@code null}
* @return {@code true} if the supplied source can be converted
*/
boolean canConvert(Object source, Class<?> targetType, ClassLoader classLoader);

/**
* Convert the supplied source object into an instance of the specified
* target type.
*
* @param source the source object to convert; may be {@code null} but only
* if the target type is a reference type
* @param targetType the target type the source should be converted into;
* never {@code null}
* @param classLoader the {@code ClassLoader} to use; never {@code null}
* @return the converted object; may be {@code null} but only if the target
* type is a reference type
* @throws ConversionException if an error occurs during the conversion
*/
Object convert(Object source, Class<?> targetType, ClassLoader classLoader) throws ConversionException;

}
Original file line number Diff line number Diff line change
@@ -10,13 +10,12 @@

package org.junit.platform.commons.support.conversion;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static org.apiguardian.api.API.Status.DEPRECATED;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType;

import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.ClassLoaderUtils;
@@ -30,17 +29,6 @@
@API(status = EXPERIMENTAL, since = "1.11")
public final class ConversionSupport {

private static final List<StringToObjectConverter> stringToObjectConverters = unmodifiableList(asList( //
new StringToBooleanConverter(), //
new StringToCharacterConverter(), //
new StringToNumberConverter(), //
new StringToClassConverter(), //
new StringToEnumConverter(), //
new StringToJavaTimeConverter(), //
new StringToCommonJavaTypesConverter(), //
new FallbackStringToObjectConverter() //
));

private ConversionSupport() {
/* no-op */
}
@@ -49,43 +37,6 @@ private ConversionSupport() {
* Convert the supplied source {@code String} into an instance of the specified
* target type.
*
* <p>If the target type is {@code String}, the source {@code String} will not
* be modified.
*
* <p>Some forms of conversion require a {@link ClassLoader}. If none is
* provided, the {@linkplain ClassLoaderUtils#getDefaultClassLoader() default
* ClassLoader} will be used.
*
* <p>This method is able to convert strings into primitive types and their
* corresponding wrapper types ({@link Boolean}, {@link Character}, {@link Byte},
* {@link Short}, {@link Integer}, {@link Long}, {@link Float}, and
* {@link Double}), enum constants, date and time types from the
* {@code java.time} package, as well as common Java types such as {@link Class},
* {@link java.io.File}, {@link java.nio.file.Path}, {@link java.nio.charset.Charset},
* {@link java.math.BigDecimal}, {@link java.math.BigInteger},
* {@link java.util.Currency}, {@link java.util.Locale}, {@link java.util.UUID},
* {@link java.net.URI}, and {@link java.net.URL}.
*
* <p>If the target type is not covered by any of the above, a convention-based
* conversion strategy will be used to convert the source {@code String} into the
* given target type by invoking a static factory method or factory constructor
* defined in the target type. The search algorithm used in this strategy is
* outlined below.
*
* <h4>Search Algorithm</h4>
*
* <ol>
* <li>Search for a single, non-private static factory method in the target
* type that converts from a String to the target type. Use the factory method
* if present.</li>
* <li>Search for a single, non-private constructor in the target type that
* accepts a String. Use the constructor if present.</li>
* </ol>
*
* <p>If multiple suitable factory methods are discovered they will be ignored.
* If neither a single factory method nor a single constructor is found, the
* convention-based conversion strategy will not apply.
*
* @param source the source {@code String} to convert; may be {@code null}
* but only if the target type is a reference type
* @param targetType the target type the source should be converted into;
@@ -97,48 +48,51 @@ private ConversionSupport() {
* type is a reference type
*
* @since 1.11
* @see DefaultConversionService
* @deprecated Use {@link #convert(Object, Class, ClassLoader)} instead.
*/
@SuppressWarnings("unchecked")
@Deprecated
@API(status = DEPRECATED, since = "5.13")
public static <T> T convert(String source, Class<T> targetType, ClassLoader classLoader) {
if (source == null) {
if (targetType.isPrimitive()) {
throw new ConversionException(
"Cannot convert null to primitive value of type " + targetType.getTypeName());
}
return null;
}
return (T) DefaultConversionService.INSTANCE.convert(source, targetType, getClassLoader(classLoader));
}

if (String.class.equals(targetType)) {
return (T) source;
}
/**
* Convert the supplied source object into an instance of the specified
* target type.
*
* @param source the source object to convert; may be {@code null}
* but only if the target type is a reference type
* @param targetType the target type the source should be converted into;
* never {@code null}
* @param classLoader the {@code ClassLoader} to use; may be {@code null} to
* use the default {@code ClassLoader}
* @param <T> the type of the target
* @return the converted object; may be {@code null} but only if the target
* type is a reference type
*
* @since 1.13
*/
@API(status = EXPERIMENTAL, since = "1.13")
@SuppressWarnings("unchecked")
public static <T> T convert(Object source, Class<T> targetType, ClassLoader classLoader) {
ClassLoader classLoaderToUse = getClassLoader(classLoader);
ServiceLoader<ConversionService> serviceLoader = ServiceLoader.load(ConversionService.class, classLoaderToUse);

Class<?> targetTypeToUse = toWrapperType(targetType);
Optional<StringToObjectConverter> converter = stringToObjectConverters.stream().filter(
candidate -> candidate.canConvertTo(targetTypeToUse)).findFirst();
if (converter.isPresent()) {
try {
ClassLoader classLoaderToUse = classLoader != null ? classLoader
: ClassLoaderUtils.getDefaultClassLoader();
return (T) converter.get().convert(source, targetTypeToUse, classLoaderToUse);
}
catch (Exception ex) {
if (ex instanceof ConversionException) {
// simply rethrow it
throw (ConversionException) ex;
}
// else
throw new ConversionException(
String.format("Failed to convert String \"%s\" to type %s", source, targetType.getTypeName()), ex);
}
}
ConversionService conversionService = Stream.concat( //
StreamSupport.stream(serviceLoader.spliterator(), false), //
Stream.of(DefaultConversionService.INSTANCE)) //
.filter(candidate -> candidate.canConvert(source, targetType, classLoader)) //
.findFirst() //
.orElseThrow(() -> new ConversionException("No registered or built-in converter for source '" + source
+ "' and target type " + targetType.getTypeName()));

throw new ConversionException(
"No built-in converter for source type java.lang.String and target type " + targetType.getTypeName());
return (T) conversionService.convert(source, targetType, classLoaderToUse);
}

private static Class<?> toWrapperType(Class<?> targetType) {
Class<?> wrapperType = getWrapperType(targetType);
return wrapperType != null ? wrapperType : targetType;
private static ClassLoader getClassLoader(ClassLoader classLoader) {
return classLoader != null ? classLoader : ClassLoaderUtils.getDefaultClassLoader();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright 2015-2025 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.commons.support.conversion;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType;

import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.ClassLoaderUtils;

/**
* {@code DefaultConversionService} is the default implementation of the
* {@link ConversionService} API.
*
* <p>The {@code DefaultConversionService} is able to convert from strings to a
* number of primitive types and their corresponding wrapper types (Byte, Short,
* Integer, Long, Float, and Double), date and time types from the
* {@code java.time} package, and some additional common Java types such as
* {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency},
* {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc.
*
* <p>If the source and target types are identical, the source object will not
* be modified.
*
* @since 1.13
*/
@API(status = INTERNAL, since = "5.13")
public class DefaultConversionService implements ConversionService {

static final DefaultConversionService INSTANCE = new DefaultConversionService();

private static final List<StringToObjectConverter> stringToObjectConverters = unmodifiableList(asList( //
new StringToBooleanConverter(), //
new StringToCharacterConverter(), //
new StringToNumberConverter(), //
new StringToClassConverter(), //
new StringToEnumConverter(), //
new StringToJavaTimeConverter(), //
new StringToCommonJavaTypesConverter(), //
new FallbackStringToObjectConverter() //
));

private DefaultConversionService() {
// nothing to initialize
}

/**
* Determine if the supplied source object can be converted into an instance
* of the specified target type.
*
* @param source the source object to convert; may be {@code null} but only
* if the target type is a reference type
* @param targetType the target type the source should be converted into;
* never {@code null}
* @param classLoader the {@code ClassLoader} to use; never {@code null}
* @return {@code true} if the supplied source can be converted
*/
@Override
public boolean canConvert(Object source, Class<?> targetType, ClassLoader classLoader) {
if (source == null) {
return !targetType.isPrimitive();
}

if (!(source instanceof String)) {
return false;
}

if (String.class.equals(targetType)) {
return true;
}

return stringToObjectConverters.stream().anyMatch(
candidate -> candidate.canConvertTo(toWrapperType(targetType)));
}

/**
* Convert the supplied source object into an instance of the specified
* target type.
*
* <p>If the target type is {@code String}, the source {@code String} will not
* be modified.
*
* <p>Some forms of conversion require a {@link ClassLoader}. If none is
* provided, the {@linkplain ClassLoaderUtils#getDefaultClassLoader() default
* ClassLoader} will be used.
*
* <p>This method is able to convert strings into primitive types and their
* corresponding wrapper types ({@link Boolean}, {@link Character}, {@link Byte},
* {@link Short}, {@link Integer}, {@link Long}, {@link Float}, and
* {@link Double}), enum constants, date and time types from the
* {@code java.time} package, as well as common Java types such as {@link Class},
* {@link java.io.File}, {@link java.nio.file.Path}, {@link java.nio.charset.Charset},
* {@link java.math.BigDecimal}, {@link java.math.BigInteger},
* {@link java.util.Currency}, {@link java.util.Locale}, {@link java.util.UUID},
* {@link java.net.URI}, and {@link java.net.URL}.
*
* <p>If the target type is not covered by any of the above, a convention-based
* conversion strategy will be used to convert the source {@code String} into the
* given target type by invoking a static factory method or factory constructor
* defined in the target type. The search algorithm used in this strategy is
* outlined below.
*
* <h4>Search Algorithm</h4>
*
* <ol>
* <li>Search for a single, non-private static factory method in the target
* type that converts from a String to the target type. Use the factory method
* if present.</li>
* <li>Search for a single, non-private constructor in the target type that
* accepts a String. Use the constructor if present.</li>
* </ol>
*
* <p>If multiple suitable factory methods are discovered, they will be ignored.
* If neither a single factory method nor a single constructor is found, the
* convention-based conversion strategy will not apply.
*
* @param source the source object to convert; may be {@code null} but only
* if the target type is a reference type
* @param targetType the target type the source should be converted into;
* never {@code null}
* @param classLoader the {@code ClassLoader} to use; never {@code null}
* @return the converted object; may be {@code null} but only if the target
* type is a reference type
* @throws ConversionException if an error occurs during the conversion
*/
@Override
public Object convert(Object source, Class<?> targetType, ClassLoader classLoader) {
if (source == null) {
if (targetType.isPrimitive()) {
throw new ConversionException(
"Cannot convert null to primitive value of type " + targetType.getTypeName());
}
return null;
}

if (String.class.equals(targetType)) {
return source;
}

Class<?> targetTypeToUse = toWrapperType(targetType);
Optional<StringToObjectConverter> converter = stringToObjectConverters.stream().filter(
candidate -> candidate.canConvertTo(targetTypeToUse)).findFirst();
if (converter.isPresent()) {
try {
return converter.get().convert((String) source, targetTypeToUse, classLoader);
}
catch (Exception ex) {
if (ex instanceof ConversionException) {
// simply rethrow it
throw (ConversionException) ex;
}
// else
throw new ConversionException(
String.format("Failed to convert String \"%s\" to type %s", source, targetType.getTypeName()), ex);
}
}

throw new ConversionException(
"No built-in converter for source type java.lang.String and target type " + targetType.getTypeName());
}

private static Class<?> toWrapperType(Class<?> targetType) {
Class<?> wrapperType = getWrapperType(targetType);
return wrapperType != null ? wrapperType : targetType;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2015-2025 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.commons.support.conversion;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import org.apiguardian.api.API;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ReflectionUtils;

/**
* {@code TypedConversionService} is an abstract base class for
* {@link ConversionService} implementations that always convert objects of a
* given source type into a given target type.
*
* @param <S> the type of the source argument to convert
* @param <T> the type of the target object to create from the source
* @since 1.13
*/
@API(status = EXPERIMENTAL, since = "1.13")
public abstract class TypedConversionService<S, T> implements ConversionService {

private final Class<S> sourceType;
private final Class<T> targetType;

/**
* Create a new {@code TypedConversionService}.
*
* @param sourceType the type of the argument to convert; never {@code null}
* @param targetType the type of the target object to create from the source;
* never {@code null}
*/
protected TypedConversionService(Class<S> sourceType, Class<T> targetType) {
this.sourceType = Preconditions.notNull(sourceType, "sourceType must not be null");
this.targetType = Preconditions.notNull(targetType, "targetType must not be null");
}

@Override
public final boolean canConvert(Object source, Class<?> targetType, ClassLoader classLoader) {
return sourceType.isInstance(source) && ReflectionUtils.isAssignableTo(this.targetType, targetType);
}

@Override
public final Object convert(Object source, Class<?> targetType, ClassLoader classLoader) {
return source == null ? convert(null) : convert(this.sourceType.cast(source));
}

/**
* Convert the supplied {@code source} object of type {@code S} into an object
* of type {@code T}.
*
* @param source the source object to convert; may be {@code null}
* @return the converted object; may be {@code null} but only if the target
* type is a reference type
* @throws ConversionException if an error occurs during the conversion
*/
protected abstract T convert(S source) throws ConversionException;

}
Original file line number Diff line number Diff line change
@@ -54,5 +54,6 @@
org.junit.platform.suite.engine,
org.junit.platform.testkit,
org.junit.vintage.engine;
uses org.junit.platform.commons.support.conversion.ConversionService;
uses org.junit.platform.commons.support.scanning.ClasspathScanner;
}
Original file line number Diff line number Diff line change
@@ -78,39 +78,29 @@ void throwsExceptionForNullToPrimitiveTypeConversion(Class<?> type) {
.isThrownBy(() -> convert(null, type)) //
.withMessage("Cannot convert null to primitive value of type " + type.getCanonicalName());

verify(underTest, never()).convert(any(), any(), any(ClassLoader.class));
}

@Test
void throwsExceptionForNonStringsConversion() {
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert(new Enigma(), String.class)) //
.withMessage("No built-in converter for source type %s and target type java.lang.String",
Enigma.class.getName());

verify(underTest, never()).convert(any(), any(), any(ClassLoader.class));
verify(underTest, never()).delegateConversion(any(), any(), any(ClassLoader.class));
}

@Test
void delegatesStringsConversion() {
doReturn(null).when(underTest).convert(any(), any(), any(ClassLoader.class));
doReturn(null).when(underTest).delegateConversion(any(), any(), any(ClassLoader.class));

convert("value", int.class);

verify(underTest).convert("value", int.class, getClassLoader(DefaultArgumentConverterTests.class));
verify(underTest).delegateConversion("value", int.class, getClassLoader(DefaultArgumentConverterTests.class));
}

@Test
void throwsExceptionForDelegatedConversionFailure() {
ConversionException exception = new ConversionException("fail");
doThrow(exception).when(underTest).convert(any(), any(), any(ClassLoader.class));
doThrow(exception).when(underTest).delegateConversion(any(), any(), any(ClassLoader.class));

assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("value", int.class)) //
.withCause(exception) //
.withMessage(exception.getMessage());

verify(underTest).convert("value", int.class, getClassLoader(DefaultArgumentConverterTests.class));
verify(underTest).delegateConversion("value", int.class, getClassLoader(DefaultArgumentConverterTests.class));
}

@Test
@@ -123,14 +113,14 @@ void delegatesStringToClassWithCustomTypeFromDifferentClassLoaderConversion() th
var declaringExecutable = ReflectionSupport.findMethod(customType, "foo").orElseThrow();
assertThat(declaringExecutable.getDeclaringClass().getClassLoader()).isSameAs(testClassLoader);

doReturn(customType).when(underTest).convert(any(), any(), any(ClassLoader.class));
doReturn(customType).when(underTest).delegateConversion(any(), any(), any(ClassLoader.class));

var clazz = (Class<?>) convert(customTypeName, Class.class, testClassLoader);
assertThat(clazz).isNotEqualTo(Enigma.class);
assertThat(clazz).isEqualTo(customType);
assertThat(clazz.getClassLoader()).isSameAs(testClassLoader);

verify(underTest).convert(customTypeName, Class.class, testClassLoader);
verify(underTest).delegateConversion(customTypeName, Class.class, testClassLoader);
}
}

@@ -143,7 +133,7 @@ private void assertConverts(Object input, Class<?> targetClass, Object expectedO
.describedAs(input + " --(" + targetClass.getName() + ")--> " + expectedOutput) //
.isEqualTo(expectedOutput);

verify(underTest, never()).convert(any(), any(), any(ClassLoader.class));
verify(underTest, never()).delegateConversion(any(), any(), any(ClassLoader.class));
}

private Object convert(Object input, Class<?> targetClass) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2015-2025 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.params.converter;

import java.util.Locale;

import org.junit.platform.commons.support.conversion.TypedConversionService;

// FIXME delete
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would make a good test case, though. We have existing tests that register services for tests using an extra class loader:

private static void withTestServices(Runnable runnable) {
var current = Thread.currentThread().getContextClassLoader();
var url = LauncherFactoryTests.class.getClassLoader().getResource("testservices/");
try (var classLoader = new URLClassLoader(new URL[] { url }, current)) {
Thread.currentThread().setContextClassLoader(classLoader);
runnable.run();
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
finally {
Thread.currentThread().setContextClassLoader(current);
}
}

We could generalize and move that method to a test utility class (e.g. in junit-jupiter-api/src/testFixtures) so it can be reused here.

public class LocaleConversionService extends TypedConversionService<String, Locale> {

public LocaleConversionService() {
super(String.class, Locale.class);
}

@Override
protected Locale convert(String source) {
return Locale.forLanguageTag(source);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.junit.jupiter.params.converter.LocaleConversionService
Original file line number Diff line number Diff line change
@@ -107,7 +107,8 @@ void convertsStringsToPrimitiveWrapperTypes() {
void throwsExceptionForNullToPrimitiveTypeConversion(Class<?> type) {
assertThatExceptionOfType(ConversionException.class) //
.isThrownBy(() -> convert(null, type)) //
.withMessage("Cannot convert null to primitive value of type " + type.getCanonicalName());
.withMessage("No registered or built-in converter for source 'null' and target type %s",
type.getCanonicalName());
}

@ParameterizedTest(name = "[{index}] {0}")
@@ -153,7 +154,7 @@ void throwsExceptionOnInvalidStringForPrimitiveTypes() {
void throwsExceptionWhenImplicitConversionIsUnsupported() {
assertThatExceptionOfType(ConversionException.class) //
.isThrownBy(() -> convert("foo", Enigma.class)) //
.withMessage("No built-in converter for source type java.lang.String and target type %s",
.withMessage("No registered or built-in converter for source 'foo' and target type %s",
Enigma.class.getName());
}

@@ -309,19 +310,19 @@ void convertsStringToUUID() {

// -------------------------------------------------------------------------

private void assertConverts(String input, Class<?> targetClass, Object expectedOutput) {
private void assertConverts(Object input, Class<?> targetClass, Object expectedOutput) {
var result = convert(input, targetClass);

assertThat(result) //
.describedAs(input + " --(" + targetClass.getName() + ")--> " + expectedOutput) //
.isEqualTo(expectedOutput);
}

private Object convert(String input, Class<?> targetClass) {
private Object convert(Object input, Class<?> targetClass) {
return convert(input, targetClass, classLoader());
}

private Object convert(String input, Class<?> targetClass, ClassLoader classLoader) {
private Object convert(Object input, Class<?> targetClass, ClassLoader classLoader) {
return ConversionSupport.convert(input, targetClass, classLoader);
}

Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ requires java.base mandated
requires java.logging
requires java.management
requires org.apiguardian.api static transitive
uses org.junit.platform.commons.support.conversion.ConversionService
uses org.junit.platform.commons.support.scanning.ClasspathScanner
qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine
qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.jfr org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.commons org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine