diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc
index 20ba0cdeed290..74c97e0735685 100644
--- a/docs/src/main/asciidoc/getting-started-testing.adoc
+++ b/docs/src/main/asciidoc/getting-started-testing.adoc
@@ -1394,3 +1394,14 @@ For `@QuarkusIntegrationTest` tests that result in launcher the application as a
This can be used by `QuarkusTestResourceLifecycleManager` that need to launch additional containers that the application will communicate with.
====
+== Troubleshooting
+
+=== Junit 5 parameterized tests failing on Java 16 and newer
+
+`@ParameterizedTest`s in JUnit 5 can fail with a `java.lang.IllegalStateException` (or
+`com.thoughtworks.xstream.converters.ConversionException` in older Quarkus versions) with a message `No converter available`
+for new-ish collection types like `java.util.Arrays$ArrayList` or other "JDK internal" collection types like
+`java.util.ImmutableCollections$List12`. The only workaround at this point is to add necessary `--add-opens` options.
+
+Since `quarkus-junit5` uses [X-stream](https://github.com/x-stream/xstream) under the covers, there's no immediate
+solution available, unfortunately.
diff --git a/integration-tests/test-extension/tests/pom.xml b/integration-tests/test-extension/tests/pom.xml
index a0c6923d8e41d..79aab17cf193d 100644
--- a/integration-tests/test-extension/tests/pom.xml
+++ b/integration-tests/test-extension/tests/pom.xml
@@ -118,6 +118,23 @@
uber-jar
+
+ jdk17
+
+ [17,)
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ --add-opens java.base/java.util=ALL-UNNAMED
+
+
+
+
+
diff --git a/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/ParamsTest.java b/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/ParamsTest.java
new file mode 100644
index 0000000000000..2c1ee739766f8
--- /dev/null
+++ b/integration-tests/test-extension/tests/src/test/java/io/quarkus/it/extension/ParamsTest.java
@@ -0,0 +1,126 @@
+package io.quarkus.it.extension;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+/**
+ * Exercise {@link ParameterizedTest @ParameterizedTest}s.
+ *
+ *
+ * This test will run into x-stream/xstream#253 on Java 16 and
+ * newer without the necessary {@code --add-opens} options.
+ */
+@QuarkusTest
+public class ParamsTest {
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ public void valuesBooleans(boolean ignore) {
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "true", "false" })
+ public void valuesStrings(String ignore) {
+ }
+
+ @ParameterizedTest
+ @ValueSource(classes = { String.class, TestData.class })
+ public void valuesClasses(Class> ignore) {
+ }
+
+ @ParameterizedTest
+ @ValueSource(chars = { 'a', 'b', 'c' })
+ public void valuesChars(char ignore) {
+ }
+
+ @ParameterizedTest
+ @ValueSource(bytes = { (byte) 1, (byte) 2, (byte) 3 })
+ public void valuesBytes(byte ignore) {
+ }
+
+ @ParameterizedTest
+ @MethodSource("testDataStreamList")
+ public void methodStreamList(List ignore) {
+ }
+
+ static Stream> testDataStreamList() {
+ return Stream.of(Arrays.asList("a"), Arrays.asList("b"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("testDataStreamListOf")
+ public void methodStreamListOf(List ignore) {
+ }
+
+ static Stream> testDataStreamListOf() {
+ return Stream.of(List.of("a"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("testDataStreamSetOf")
+ public void methodStreamListOf(Set ignore) {
+ }
+
+ static Stream> testDataStreamSetOf() {
+ return Stream.of(Set.of("a"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("testDataStreamArrayList")
+ public void methodStreamArrayList(List ignore) {
+ }
+
+ static Stream> testDataStreamArrayList() {
+ return Stream.of(Collections.emptyList());
+ }
+
+ @ParameterizedTest
+ @MethodSource("testDataStream")
+ public void methodStream(TestData ignore) {
+ }
+
+ static Stream testDataStream() {
+ return Stream.of(new TestData());
+ }
+
+ @ParameterizedTest
+ @MethodSource("testDataList")
+ public void methodList(TestData ignore) {
+ }
+
+ static List testDataList() {
+ return List.of(new TestData());
+ }
+
+ @ParameterizedTest
+ @MethodSource("testDataStreamArguments")
+ public void methodList(TestData ignore, String ignored) {
+ }
+
+ static Stream testDataStreamArguments() {
+ return Stream.of(Arguments.of(new TestData(), "foo"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("testDataListArguments")
+ public void methodListArguments(TestData ignore, String ignored) {
+ }
+
+ static List testDataListArguments() {
+ return Arrays.asList(Arguments.of(new TestData(), "foo"), Arguments.of(new TestData(), "foo"));
+ }
+
+ @SuppressWarnings("unused")
+ static class TestData {
+ final List foo = Arrays.asList("one", "two", "three");
+ }
+}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java
index dc945e5bf7f7c..564608dd7af6e 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java
@@ -3,12 +3,15 @@
import java.util.function.Supplier;
import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.ConversionException;
/**
* Super simple cloning strategy that just serializes to XML and deserializes it using xstream
*/
class XStreamDeepClone implements DeepClone {
+ static final String DEFAULT_ACTION = "Please report the issue on the Quarkus issue tracker.";
+
private final Supplier xStreamSupplier;
XStreamDeepClone(ClassLoader classLoader) {
@@ -46,12 +49,29 @@ public Object get() {
private Object doClone(Object objectToClone) {
XStream xStream = xStreamSupplier.get();
- final String serialized = xStream.toXML(objectToClone);
+ final String serialized = serialize(objectToClone, xStream);
final Object result = xStream.fromXML(serialized);
if (result == null) {
- throw new IllegalStateException("Unable to deep clone object of type '" + objectToClone.getClass().getName()
- + "'. Please report the issue on the Quarkus issue tracker.");
+ throw new IllegalStateException(cannotDeepCloneMessage(objectToClone, DEFAULT_ACTION));
}
return result;
}
+
+ private String serialize(Object objectToClone, XStream xStream) {
+ try {
+ return xStream.toXML(objectToClone);
+ } catch (ConversionException e) {
+ String additional = e.getMessage().startsWith("No converter available")
+ ? "'No converter available' messages are known to occur with new-ish Java versions due " +
+ "to https://github.com/x-stream/xstream/issues/253, please try with JVM options proposed in " +
+ "the message of the nested ConversionException, " +
+ "if any, or report the issue on the Quarkus issue tracker.\n" + e.getMessage()
+ : DEFAULT_ACTION;
+ throw new IllegalStateException(cannotDeepCloneMessage(objectToClone, additional), e);
+ }
+ }
+
+ private String cannotDeepCloneMessage(Object objectToClone, String additional) {
+ return String.format("Unable to deep clone object of type '%s'. %s", objectToClone.getClass().getName(), additional);
+ }
}