From 233cec24a555d14797f752b9b16b5b7c0ec4eaad Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 20 Mar 2020 10:14:27 +0200 Subject: [PATCH 1/2] Ensure that @QuarkusTest plays nicely with @TestFactory Fixes: #8004 --- .../quarkus/it/main/DynamicTestsTestCase.java | 39 +++++++++++ .../test/junit/QuarkusDynamicTest.java | 66 +++++++++++++++++++ .../quarkus/test/junit/QuarkusExecutable.java | 26 ++++++++ .../test/junit/QuarkusTestExtension.java | 25 ++++++- 4 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 integration-tests/main/src/test/java/io/quarkus/it/main/DynamicTestsTestCase.java create mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusDynamicTest.java create mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusExecutable.java diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/DynamicTestsTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/DynamicTestsTestCase.java new file mode 100644 index 0000000000000..8928ee0bbe9c2 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/DynamicTestsTestCase.java @@ -0,0 +1,39 @@ +package io.quarkus.it.main; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; + +import io.quarkus.it.arc.UnusedBean; +import io.quarkus.test.junit.QuarkusDynamicTest; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class DynamicTestsTestCase { + + @Inject + UnusedBean bean; + + @Test + public void testInjection() { + assertNotNull(bean); + } + + @TestFactory + public List dynamicTests() { + return Arrays.asList( + QuarkusDynamicTest.dynamicTest("test 1", () -> { + assertNotNull(bean); + }), + QuarkusDynamicTest.dynamicTest("test 2", () -> { + assertEquals(1, 1); + })); + } +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusDynamicTest.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusDynamicTest.java new file mode 100644 index 0000000000000..b07a34257b678 --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusDynamicTest.java @@ -0,0 +1,66 @@ +package io.quarkus.test.junit; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.junit.jupiter.api.function.Executable; + +/** + * This class needs to be used when users want to use JUnit 5's {@link org.junit.jupiter.api.DynamicTest} along with + * {@link org.junit.jupiter.api.TestFactory} in a {@link QuarkusTest}. + * + * + * An example usage in a test is: + * + *
+ * @TestFactory
+ * public List dynamicTests() {
+ *   return Arrays.asList(
+ *     QuarkusDynamicTest.dynamicTest("test 1", () -> {
+ *       assertEquals(1, 1);
+ *       // of course more complex things can be done here, like accessing a field injected with @Inject
+ *     }),
+ *     QuarkusDynamicTest.dynamicTest("test 2", () -> {
+ *       assertEquals(2, 2);
+ *     })
+ *   );
+ * }
+ * 
+ */ +public class QuarkusDynamicTest { + + private static ClassLoader cl; + private static Method dynamicTestMethod; + private static Constructor quarkusExecutableClassConstructor; + + public static Object dynamicTest(String displayName, Executable executable) { + try { + Object executableInstance = getQuarkusExecutableClassConstructor().newInstance(executable); + return getDynamicTestMethod().invoke(null, displayName, executableInstance); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + static void setClassLoader(ClassLoader classLoader) { + cl = classLoader; + } + + private static Method getDynamicTestMethod() throws ClassNotFoundException, NoSuchMethodException { + if (dynamicTestMethod == null) { + dynamicTestMethod = Class.forName("org.junit.jupiter.api.DynamicTest", false, cl) + .getMethod("dynamicTest", String.class, Class + .forName("org.junit.jupiter.api.function.Executable", false, cl)); + } + return dynamicTestMethod; + } + + private static Constructor getQuarkusExecutableClassConstructor() throws ClassNotFoundException, NoSuchMethodException { + if (quarkusExecutableClassConstructor == null) { + quarkusExecutableClassConstructor = Class.forName("io.quarkus.test.junit.QuarkusExecutable", false, cl) + .getConstructor(Object.class); + } + return quarkusExecutableClassConstructor; + } + +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusExecutable.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusExecutable.java new file mode 100644 index 0000000000000..b691d43dcf6a7 --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusExecutable.java @@ -0,0 +1,26 @@ +package io.quarkus.test.junit; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.function.Executable; + +public class QuarkusExecutable implements Executable { + + private final Object executable; + private final Method executeMethod; + + public QuarkusExecutable(Object executable) { + this.executable = executable; + try { + this.executeMethod = executable.getClass().getMethod("execute"); + this.executeMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public void execute() throws Throwable { + executeMethod.invoke(executable); + } +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index b4fd2185dabd1..8afe005907f36 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -102,6 +102,8 @@ private ExtensionState doJavaStart(ExtensionContext context) throws Throwable { .newInstance(context.getRequiredTestClass()); testResourceManager.getClass().getMethod("start").invoke(testResourceManager); + handleDynamicTests(startupAction.getClassLoader()); + runningQuarkusApplication = startupAction.run(); ConfigProviderResolver.setInstance(new RunningAppConfigResolver(runningQuarkusApplication)); @@ -153,6 +155,14 @@ public void run() { } } + private void handleDynamicTests(ClassLoader startupActionClassLoader) + throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Class quarkusDynamicTestClass = startupActionClassLoader.loadClass(QuarkusDynamicTest.class.getName()); + Method setClassLoaderMethod = quarkusDynamicTestClass.getDeclaredMethod("setClassLoader", ClassLoader.class); + setClassLoaderMethod.setAccessible(true); + setClassLoaderMethod.invoke(null, originalCl); + } + @Override public void afterEach(ExtensionContext context) throws Exception { if (isNativeTest(context)) { @@ -320,6 +330,17 @@ public void interceptTestTemplateMethod(Invocation invocation, ReflectiveI invocation.skip(); } + @Override + public T interceptTestFactoryMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + if (isNativeTest(extensionContext)) { + return invocation.proceed(); + } + T result = (T) runExtensionMethod(invocationContext, extensionContext); + invocation.skip(); + return result; + } + @Override public void interceptAfterEachMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { @@ -342,7 +363,7 @@ public void interceptAfterAllMethod(Invocation invocation, ReflectiveInvoc invocation.skip(); } - private void runExtensionMethod(ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) + private Object runExtensionMethod(ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { Method newMethod = null; @@ -389,7 +410,7 @@ private void runExtensionMethod(ReflectiveInvocationContext invocationCo } } - newMethod.invoke(actualTestInstance, argumentsFromTccl.toArray(new Object[0])); + return newMethod.invoke(actualTestInstance, argumentsFromTccl.toArray(new Object[0])); } catch (InvocationTargetException e) { throw e.getCause(); } catch (IllegalAccessException | ClassNotFoundException e) { From 5e40439425f07e38ecb5d7545b146b9f4374bb38 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 23 Mar 2020 11:21:40 +1100 Subject: [PATCH 2/2] Load JUnit parent first Co-authored-by: George Gastaldi > --- core/runtime/pom.xml | 5 ++ .../quarkus/it/main/DynamicTestsTestCase.java | 6 +- .../test/junit/QuarkusDynamicTest.java | 66 ------------------- .../quarkus/test/junit/QuarkusExecutable.java | 26 -------- .../test/junit/QuarkusTestExtension.java | 10 --- 5 files changed, 8 insertions(+), 105 deletions(-) delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusDynamicTest.java delete mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusExecutable.java diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 9edd8f92208c1..439cb05b18a1f 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -120,6 +120,11 @@ org.objenesis:objenesis net.bytebuddy:byte-buddy net.bytebuddy:byte-buddy-agent + org.junit.jupiter:junit-jupiter-api + org.junit.jupiter:junit-jupiter-engine + org.junit.jupiter:junit-jupiter-params + org.junit.platform:junit-platform-commons + org.junit.platform:junit-platform-engine io.smallrye:smallrye-config diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/DynamicTestsTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/DynamicTestsTestCase.java index 8928ee0bbe9c2..875324903111b 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/DynamicTestsTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/DynamicTestsTestCase.java @@ -8,11 +8,11 @@ import javax.inject.Inject; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import io.quarkus.it.arc.UnusedBean; -import io.quarkus.test.junit.QuarkusDynamicTest; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest @@ -29,10 +29,10 @@ public void testInjection() { @TestFactory public List dynamicTests() { return Arrays.asList( - QuarkusDynamicTest.dynamicTest("test 1", () -> { + DynamicTest.dynamicTest("test 1", () -> { assertNotNull(bean); }), - QuarkusDynamicTest.dynamicTest("test 2", () -> { + DynamicTest.dynamicTest("test 2", () -> { assertEquals(1, 1); })); } diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusDynamicTest.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusDynamicTest.java deleted file mode 100644 index b07a34257b678..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusDynamicTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.quarkus.test.junit; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import org.junit.jupiter.api.function.Executable; - -/** - * This class needs to be used when users want to use JUnit 5's {@link org.junit.jupiter.api.DynamicTest} along with - * {@link org.junit.jupiter.api.TestFactory} in a {@link QuarkusTest}. - * - * - * An example usage in a test is: - * - *
- * @TestFactory
- * public List dynamicTests() {
- *   return Arrays.asList(
- *     QuarkusDynamicTest.dynamicTest("test 1", () -> {
- *       assertEquals(1, 1);
- *       // of course more complex things can be done here, like accessing a field injected with @Inject
- *     }),
- *     QuarkusDynamicTest.dynamicTest("test 2", () -> {
- *       assertEquals(2, 2);
- *     })
- *   );
- * }
- * 
- */ -public class QuarkusDynamicTest { - - private static ClassLoader cl; - private static Method dynamicTestMethod; - private static Constructor quarkusExecutableClassConstructor; - - public static Object dynamicTest(String displayName, Executable executable) { - try { - Object executableInstance = getQuarkusExecutableClassConstructor().newInstance(executable); - return getDynamicTestMethod().invoke(null, displayName, executableInstance); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - static void setClassLoader(ClassLoader classLoader) { - cl = classLoader; - } - - private static Method getDynamicTestMethod() throws ClassNotFoundException, NoSuchMethodException { - if (dynamicTestMethod == null) { - dynamicTestMethod = Class.forName("org.junit.jupiter.api.DynamicTest", false, cl) - .getMethod("dynamicTest", String.class, Class - .forName("org.junit.jupiter.api.function.Executable", false, cl)); - } - return dynamicTestMethod; - } - - private static Constructor getQuarkusExecutableClassConstructor() throws ClassNotFoundException, NoSuchMethodException { - if (quarkusExecutableClassConstructor == null) { - quarkusExecutableClassConstructor = Class.forName("io.quarkus.test.junit.QuarkusExecutable", false, cl) - .getConstructor(Object.class); - } - return quarkusExecutableClassConstructor; - } - -} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusExecutable.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusExecutable.java deleted file mode 100644 index b691d43dcf6a7..0000000000000 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusExecutable.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.quarkus.test.junit; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.function.Executable; - -public class QuarkusExecutable implements Executable { - - private final Object executable; - private final Method executeMethod; - - public QuarkusExecutable(Object executable) { - this.executable = executable; - try { - this.executeMethod = executable.getClass().getMethod("execute"); - this.executeMethod.setAccessible(true); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public void execute() throws Throwable { - executeMethod.invoke(executable); - } -} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 8afe005907f36..e273ae8a28795 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -102,8 +102,6 @@ private ExtensionState doJavaStart(ExtensionContext context) throws Throwable { .newInstance(context.getRequiredTestClass()); testResourceManager.getClass().getMethod("start").invoke(testResourceManager); - handleDynamicTests(startupAction.getClassLoader()); - runningQuarkusApplication = startupAction.run(); ConfigProviderResolver.setInstance(new RunningAppConfigResolver(runningQuarkusApplication)); @@ -155,14 +153,6 @@ public void run() { } } - private void handleDynamicTests(ClassLoader startupActionClassLoader) - throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Class quarkusDynamicTestClass = startupActionClassLoader.loadClass(QuarkusDynamicTest.class.getName()); - Method setClassLoaderMethod = quarkusDynamicTestClass.getDeclaredMethod("setClassLoader", ClassLoader.class); - setClassLoaderMethod.setAccessible(true); - setClassLoaderMethod.invoke(null, originalCl); - } - @Override public void afterEach(ExtensionContext context) throws Exception { if (isNativeTest(context)) {