diff --git a/org.eclipse.jdt.ui.tests/META-INF/MANIFEST.MF b/org.eclipse.jdt.ui.tests/META-INF/MANIFEST.MF
index 029b52d3035..01ec82f15f5 100644
--- a/org.eclipse.jdt.ui.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.ui.tests/META-INF/MANIFEST.MF
@@ -78,7 +78,8 @@ Require-Bundle:
Bundle-RequiredExecutionEnvironment: JavaSE-17
Eclipse-BundleShape: dir
Bundle-ActivationPolicy: lazy
-Import-Package: org.junit.jupiter.api,
+Import-Package: org.assertj.core.api;version="3.24.2",
+ org.junit.jupiter.api,
org.junit.platform.suite.api,
org.junit.platform.suite.commons;status=INTERNAL,
org.junit.platform.suite.engine;status=INTERNAL
diff --git a/org.eclipse.jdt.ui.tests/testresources/testClasses/JupiterTests.java b/org.eclipse.jdt.ui.tests/testresources/testClasses/JupiterTests.java
new file mode 100644
index 00000000000..a3a273a1f83
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests/testresources/testClasses/JupiterTests.java
@@ -0,0 +1,76 @@
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestTemplate;
+
+import junit.framework.TestCase;
+
+/**
+ * This class contains some internal classes that should be found/not found by the
+ * JUnit5TestFinder.
+ *
+ * The names of the classes give a hint about whether or not the class should be found and the
+ * reason for that. In order to be discovered, a class needs to fulfill these requirements:
+ *
+ * - It should be visible (it can not be
private
)
+ * - It must have tests in it.
+ *
+ *
+ * Whether or not the class can be instantiated (i.e. if they have a non-private empty
+ * constructor) does not play a role in the discoverability, but running tests on that class will
+ * throw an exception,
+ */
+class JupiterTests {
+ /**
+ * Methods using this annotation are also considered tests
+ */
+ @Test
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface CustomTestAnnotation {}
+
+ static class FoundStatic {
+ @Test void myTest() {}
+ }
+
+ static class FoundStaticCustomTestAnnotation {
+ @CustomTestAnnotation void myTest() {}
+ }
+
+ private static class NotFoundPrivate {
+ @CustomTestAnnotation void myTest() {}
+ }
+
+ static class NotFoundHasNoTests {}
+
+ static class FoundExtendsTestCase extends TestCase {
+ @Test void myTest() {}
+ }
+
+ static class FoundExtendsTestCaseCustomTestAnnotation extends TestCase {
+ @CustomTestAnnotation public void myTest() {}
+ }
+
+ private static class NotFoundPrivateExtendsTestCase extends TestCase {
+ @Test public void myTest() {}
+ }
+
+ static class FoundTestTemplateClass {
+ @TestTemplate void myTestTemplate() {}
+ }
+
+ static abstract class NotFoundAbstractWithInnerClass {
+ class NotFoundInnerInstanceClassWithTest {
+ @Test void myTest() {}
+ }
+ }
+
+ static class NotFoundExtendsAbstractWithInnerWithTest extends NotFoundAbstractWithInnerClass {}
+
+ static class FoundHasInnerClassWithNested {
+ @Nested class FoundExtendsAbstractWithNested extends NotFoundAbstractWithInnerClass {
+ @Test void myTest() {}
+ }
+ }
+}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/junit/tests/JUnit5TestFinderJupiterTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/junit/tests/JUnit5TestFinderJupiterTest.java
new file mode 100644
index 00000000000..70fe5d14d24
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/junit/tests/JUnit5TestFinderJupiterTest.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.junit.tests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import org.eclipse.jdt.junit.JUnitCore;
+import org.eclipse.jdt.testplugin.JavaProjectHelper;
+
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+
+import org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder;
+
+
+/**
+ * Test if the JUnit5TestFinder
can find tests annotated with the API of JUnit5
+ * (Jupiter)
+ */
+@RunWith(Parameterized.class)
+public class JUnit5TestFinderJupiterTest {
+
+ record TestScenario(String testClass, int testTypesCount) {
+ }
+
+ private static final String JAVA_CLASS_NAME= "JupiterTests";
+
+ private static final String JAVA_FILE_NAME= JAVA_CLASS_NAME + ".java";
+
+ private static final Path TEST_CLASS_FILE= Path.of("testresources").resolve("testClasses").resolve(JAVA_FILE_NAME);
+
+ private static IJavaProject javaProject;
+
+ @Parameters(name= "{0}")
+ public static Collection getCompilationUnits() {
+
+ // These are the current "valid" results
+ return List.of(new TestScenario(JAVA_CLASS_NAME, 7), //
+ new TestScenario("CustomTestAnnotation", 0), // Not a test class
+ new TestScenario("FoundStatic", 1), //
+ new TestScenario("FoundStaticCustomTestAnnotation", 1), //
+ new TestScenario("NotFoundPrivate", 0), // private class
+ new TestScenario("NotFoundHasNoTests", 0), // empty class (no tests)
+ new TestScenario("FoundExtendsTestCase", 1), //
+ new TestScenario("FoundExtendsTestCaseCustomTestAnnotation", 1), //
+ new TestScenario("NotFoundPrivateExtendsTestCase", 0), // private class
+ new TestScenario("FoundTestTemplateClass", 1), //
+ new TestScenario("NotFoundAbstractWithInnerClass", 0), // can't be instantiated
+ new TestScenario("NotFoundExtendsAbstractWithInnerWithTest", 0), // FIXME: why isn't this one found even though it can be instantiated?
+ new TestScenario("FoundHasInnerClassWithNested", 1), //
+ new TestScenario("NotFoundInnerInstanceClassWithTest", 0), // has test but it can't be instantiated (needs enclosing instance)
+ new TestScenario("FoundExtendsAbstractWithNested", 1) //
+ );
+
+ }
+
+ @Parameter
+ public TestScenario scenario;
+
+ private static ICompilationUnit compilationUnit;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ javaProject= JavaProjectHelper.createJavaProject("TestProject", "bin");
+ JavaProjectHelper.addRTJar(javaProject);
+ IClasspathEntry cpe= JavaCore.newContainerEntry(JUnitCore.JUNIT5_CONTAINER_PATH);
+ JavaProjectHelper.addToClasspath(javaProject, cpe);
+ JavaProjectHelper.set18CompilerOptions(javaProject);
+
+ IPackageFragmentRoot root= JavaProjectHelper.addSourceContainer(javaProject, "src");
+ IPackageFragment packageFragment= root.createPackageFragment("somepackage", true, null);
+
+ compilationUnit= createCompilationUnit(packageFragment);
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ JavaProjectHelper.delete(javaProject);
+ }
+
+ @Test
+ public void testFindTestsInContainer() throws Exception {
+ IType type= findTypeWithName(scenario.testClass());
+ Set foundTestTypes= new HashSet<>();
+
+ JUnit5TestFinder objectUnderTest= new JUnit5TestFinder();
+ objectUnderTest.findTestsInContainer(type, foundTestTypes, null);
+
+ assertThat(foundTestTypes).hasSize(scenario.testTypesCount());
+ }
+
+
+ private IType findTypeWithName(String name) throws JavaModelException {
+ for (IType type : compilationUnit.getAllTypes()) {
+ if (type.getElementName().equals(name))
+ return type;
+ }
+ return null;
+ }
+
+ private static ICompilationUnit createCompilationUnit(IPackageFragment packageFragment) throws IOException {
+ String content= Files.readString(TEST_CLASS_FILE);
+
+ try {
+ return packageFragment.createCompilationUnit(JAVA_FILE_NAME, content, false, null);
+ } catch (JavaModelException e) {
+ // let the test fail
+ throw new RuntimeException(e);
+ }
+ }
+}