For advanced scenarios where command-line or IDE filtering is insufficient, JUnit 5 provides a programmatic API through the JUnit Platform Launcher. This allows you to create custom test runners, implement complex filtering logic, and integrate testing into custom build systems or tools.
When to Use Programmatic Filtering
Programmatic filtering is useful for:
- Custom test runners with dynamic test selection
- Integration with proprietary build systems
- Complex filtering logic not supported by standard tools
- Generating custom test reports and analytics
- Creating specialized testing tools or plugins
Introduction to Luncher Api
The Launcher API is the programmatic entry point to the JUnit Platform. All runners (Maven, Gradle, IDEs) use it and eventually do the following steps:
(1) Add following extra maven dependency
pom.xml<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.10.0</version>
</dependency>
(2) Selecting Tests (Discovery) and filtering
LauncherDiscoveryRequest request =
LauncherDiscoveryRequestBuilder.request()
.selectors(
DiscoverySelectors.selectClass(...),
DiscoverySelectors.selectPackage(.....))
.filters(
TagFilter.includeTags("<filter expression>"),
TagFilter.excludeTags("<filter expression>"))
.build();
(3) Creating the Launcher
Launcher launcher = LauncherFactory.create();
(4) Executing Tests
launcher.execute(request);
Example
Test classes
package com.logicbig.example;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("calc-test")
class MyCalcTest {
@Test
void primeNumberTest() {
assertEquals(2, 1 + 1);
}
@Test
void sumTest() {
assertTrue(true);
}
}
package com.logicbig.example;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("integration")
class MyIntegrationTest {
@Test
void dbTransactionTest() {
assertTrue(true);
}
@Test
@Tag("staging")
void stagingTest() {
assertEquals(2, 1 + 1);
}
@Test
@Tag("calc-test")
void testFactorial(){
assertTrue(true);
}
}
Using Luncher Api
package com.logicbig.example;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.TagFilter;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
public class TagFilteringLauncher {
public static void main(String[] args) {
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
.selectors(
DiscoverySelectors.selectClass(MyIntegrationTest.class),
DiscoverySelectors.selectClass(MyCalcTest.class)
)
.filters(
TagFilter.includeTags("integration | calc-test"),
TagFilter.excludeTags("staging")
)
.build();
Launcher launcher = LauncherFactory.create();
// Create summary listener
SummaryGeneratingListener listener = new SummaryGeneratingListener(){
//to print what test passed
@Override
public void executionFinished(TestIdentifier testIdentifier,
TestExecutionResult testExecutionResult) {
if (testIdentifier.isTest()
&& testExecutionResult.getStatus() == TestExecutionResult.Status.SUCCESSFUL) {
System.out.println("PASSED: " + testIdentifier.getDisplayName());
}
}
};
// Execute with listener
launcher.execute(request, listener);
// Print summary
TestExecutionSummary summary = listener.getSummary();
System.out.println("=================================");
System.out.println("Test run finished");
System.out.println("Tests found : " + summary.getTestsFoundCount());
System.out.println("Tests started : " + summary.getTestsStartedCount());
System.out.println("Tests passed : " + summary.getTestsSucceededCount());
System.out.println("Tests failed : " + summary.getTestsFailedCount());
System.out.println("Tests skipped : " + summary.getTestsSkippedCount());
System.out.println("=================================");
}
}
OutputPASSED: testFactorial() PASSED: dbTransactionTest() PASSED: sumTest() PASSED: primeNumberTest() ================================= Test run finished Tests found : 4 Tests started : 4 Tests passed : 0 Tests failed : 0 Tests skipped : 0 =================================
Example ProjectDependencies and Technologies Used: - junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
Version Compatibility: 5.2.0 - 6.0.1 Version compatibilities of junit-jupiter-engine with this example:
- 5.2.0
- 5.3.0
- 5.3.1
- 5.3.2
- 5.4.0
- 5.4.1
- 5.4.2
- 5.5.0
- 5.5.1
- 5.5.2
- 5.6.0
- 5.6.1
- 5.6.2
- 5.6.3
- 5.7.0
- 5.7.1
- 5.7.2
- 5.8.0
- 5.8.1
- 5.8.2
- 5.9.0
- 5.9.1
- 5.9.2
- 5.9.3
- 5.10.0
- 5.10.1
- 5.10.2
- 5.10.3
- 5.10.4
- 5.10.5
- 5.11.0
- 5.11.1
- 5.11.2
- 5.11.3
- 5.11.4
- 5.12.0
- 5.12.1
- 5.12.2
- 5.13.0
- 5.13.1
- 5.13.2
- 5.13.3
- 5.13.4
- 5.14.0
- 5.14.1
- 6.0.0
- 6.0.1
Versions in green have been tested.
- junit-platform-launcher 6.0.1 (Module "junit-platform-launcher" of JUnit)
- JDK 25
- Maven 3.9.11
|