diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc index d5fbb3c02d43..17f24bed767a 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc @@ -60,6 +60,8 @@ repository on GitHub. a custom comment character using the new `commentCharacter` attribute. * Improve error message when using `@ParameterizedClass` with field injection and not providing enough arguments. +* Allow calling `TypedArgumentConverter` constructor for `@Nullable T` target types + without having to cast class literals to `Class<@Nullable T>`. [[release-notes-6.0.1-junit-vintage]] diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java index 37ad452ac19d..3b77849b8e52 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; @@ -43,7 +44,8 @@ public abstract class TypedArgumentConverter impl * @param targetType the type of the target object to create from the source; * never {@code null} */ - protected TypedArgumentConverter(Class sourceType, Class targetType) { + protected TypedArgumentConverter(Class sourceType, + @SuppressWarnings("NullableProblems") Class<@NonNull T> targetType) { this.sourceType = Preconditions.notNull(sourceType, "sourceType must not be null"); this.targetType = Preconditions.notNull(targetType, "targetType must not be null"); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java index df50b84e4262..85844d87f54a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java @@ -57,6 +57,7 @@ import java.util.stream.Stream; import org.assertj.core.api.Condition; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -972,7 +973,8 @@ void test2() { } } - private static class CustomIntegerToStringConverter extends TypedArgumentConverter { + @NullMarked + private static class CustomIntegerToStringConverter extends TypedArgumentConverter { CustomIntegerToStringConverter() { super(Integer.class, String.class); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 18f7ec9dd6ae..bec61ba12bab 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -63,6 +63,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -2629,6 +2630,7 @@ void testWithIso639(@ConvertWith(Iso639Converter.class) Locale locale) { assertEquals("", locale.getCountry()); } + @NullMarked static class Iso639Converter extends TypedArgumentConverter { Iso639Converter() { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java index 7e54bd569130..3ada05c07bab 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java @@ -24,6 +24,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -203,6 +204,7 @@ void stringToPrimitiveLong(@StringLength long length) { private @interface StringLength { } + @NullMarked private static class StringLengthArgumentConverter extends TypedArgumentConverter { StringLengthArgumentConverter() { diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/converter/TypedArgumentConverterKotlinTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/converter/TypedArgumentConverterKotlinTests.kt new file mode 100644 index 000000000000..fec7835a7fbc --- /dev/null +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/converter/TypedArgumentConverterKotlinTests.kt @@ -0,0 +1,40 @@ +/* + * 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 org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNull +import org.junit.jupiter.api.extension.ParameterContext +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` + +class TypedArgumentConverterKotlinTests { + @Test + fun converts() { + val parameterContext = mock(ParameterContext::class.java) + val parameter = this.javaClass.getDeclaredMethod("foo", String::class.java).parameters[0] + `when`(parameterContext.parameter).thenReturn(parameter) + + assertNull(NullableTypeConverter().convert(null, parameterContext)) + assertEquals("null", NonNullableTypeConverter().convert(null, parameterContext)) + } + + @Suppress("unused") + private fun foo(param: String) = Unit + + class NullableTypeConverter : TypedArgumentConverter(String::class.java, String::class.java) { + override fun convert(source: String?) = source + } + + class NonNullableTypeConverter : TypedArgumentConverter(String::class.java, String::class.java) { + override fun convert(source: String?) = source.toString() + } +}