Last active
February 14, 2020 18:45
-
-
Save joeyslalom/e93f3e2fbea929fe59248f8a24e145ee to your computer and use it in GitHub Desktop.
Running JUnit5 as an application
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@SpringBootApplication | |
public class IntegrationTestApp { | |
public static void main(String[] args) { | |
ConfigurableApplicationContext appContext = new SpringApplicationBuilder() | |
.initializers((ApplicationContextInitializer<GenericApplicationContext>) context -> | |
context.registerBean(Junit5Runner.class, | |
() -> new Junit5Runner(ExplicitlyDeclaredTest.class)) | |
) | |
.sources(IntegrationTestApp.class) | |
.run(args); | |
System.exit(SpringApplication.exit(appContext)); | |
} | |
private static class Junit5Runner implements ApplicationRunner, ExitCodeGenerator { | |
private static final Logger LOG = LoggerFactory.getLogger(Junit5Runner.class); | |
private final List<Class<?>> testClasses; | |
private int exitCode = 0; | |
private Junit5Runner(Class<?>... testClasses) { | |
this.testClasses = Arrays.asList(testClasses); | |
} | |
@Override | |
public void run(ApplicationArguments args) { | |
DiscoverySelector[] selectors = testClasses.stream() | |
.map(DiscoverySelectors::selectClass) | |
.collect(Collectors.toList()) | |
.toArray(new DiscoverySelector[testClasses.size()]); | |
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() | |
.selectors(selectors) | |
.build(); | |
Launcher launcher = LauncherFactory.create(); | |
logTests(launcher, request); | |
SummaryGeneratingListener listener = new SummaryGeneratingListener(); | |
LoggingListener logListener = LoggingListener.forBiConsumer((t, ss) -> { | |
String msg = ss.get(); | |
if (t == null) { | |
LOG.info("test message={}", msg); | |
} else { | |
LOG.error("test error message={}", msg, t); | |
} | |
}); | |
launcher.registerTestExecutionListeners(listener, logListener); | |
launcher.execute(request); | |
ByteArrayOutputStream stream = new ByteArrayOutputStream(); | |
PrintWriter writer = new PrintWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8)); | |
if (listener.getSummary().getTotalFailureCount() > 0) { | |
listener.getSummary().printFailuresTo(writer); | |
LOG.error("JUnit Failures={}", stream); | |
exitCode = 2; | |
} | |
listener.getSummary().printTo(writer); | |
LOG.info("JUnit Summary={}", stream); | |
} | |
// log tests found for the request, optional | |
private static void logTests(Launcher launcher, LauncherDiscoveryRequest request) { | |
TestPlan testPlan = launcher.discover(request); | |
testPlan.getRoots().stream() | |
.flatMap(testId -> testPlan.getDescendants(testId).stream()) | |
.filter(testId -> testId.getType() == TestDescriptor.Type.TEST) | |
.forEach(testId -> | |
LOG.info("uniqueId={} displayName={}", testId.getUniqueId(), testId.getDisplayName())); | |
} | |
@Override | |
public int getExitCode() { | |
return exitCode; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@SpringBootApplication | |
class IntegrationTestApp | |
fun main(args: Array<String>) { | |
val context = SpringApplicationBuilder().initializers( | |
beans { | |
bean { | |
Junit5Runner(listOf(IntegrationTests::class.java)) | |
} | |
} | |
).sources(IntegrationTestApp::class.java).run(*args); | |
exitProcess(SpringApplication.exit(context)) | |
} | |
class Junit5Runner(private val testClasses: List<Class<*>>) : ApplicationRunner, ExitCodeGenerator { | |
private val log = LoggerFactory.getLogger(Junit5Runner::class.java) | |
private var exitCode = 0 | |
override fun getExitCode(): Int = exitCode | |
override fun run(args: ApplicationArguments?) { | |
val selectors = testClasses.map { DiscoverySelectors.selectClass(it) }.toTypedArray() | |
val request = LauncherDiscoveryRequestBuilder.request() | |
.selectors(*selectors) | |
.build() | |
val launcher = LauncherFactory.create() | |
logTests(launcher, request) | |
val listener = SummaryGeneratingListener() | |
val logListener = LoggingListener.forBiConsumer { t, u -> | |
val msg = u.get() | |
if (t == null) { | |
log.info("test message=$msg") | |
} else { | |
log.error("test error message=$msg", t) | |
} | |
} | |
launcher.registerTestExecutionListeners(listener, logListener) | |
launcher.execute(request) | |
val stream = ByteArrayOutputStream() | |
val writer = PrintWriter(stream) | |
if (listener.summary.totalFailureCount > 0) { | |
listener.summary.printFailuresTo(writer) | |
log.error("Junit Failures:$stream") | |
exitCode = 2 | |
} | |
listener.summary.printTo(writer) | |
log.info("Junit Summary:$stream") | |
} | |
// logs tests found for the request, optional | |
private fun logTests(launcher: Launcher, request: LauncherDiscoveryRequest) { | |
val testPlan = launcher.discover(request) | |
testPlan.roots | |
.flatMap { testPlan.getDescendants(it) } | |
.filter { it.type == TestDescriptor.Type.TEST } | |
.forEach { | |
log.info("uniqueId=${it.uniqueId} displayName=${it.displayName}") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
When creating integration tests, JUnit5 works great; developers are familiar with its constructs (e.g.,
@Before
) and its included assertions. Currently@SpringBootTest
run well in the context of Maven or Gradle, so test classes can leverage Spring@Bean
s.My goal is to continue using these tests as another application - outside of a build tool, and to package as a Spring uber jar. This simple Spring Boot app is my solution, which basically replicates JUnit5's
ConsoleLauncher
.Implementation notes:
Junit5Runner
is not a@Component
because this will cause Spring to recursesrc/main
, NOTsrc/test
. Otherwise, the Spring Boot plugin will not include them in the classpath of the bootJar (BOOT-INF/classes
). Also:Junit5Runner
. Hence, they also cannot be insrc/test
. By explicitly naming the classes, they are loaded into the classpath and allows the JUnit5Launcher
to discover them.DiscoverySelectors.selectPackage
working when run by./gradlew bootRun
, but no classes are found when run viajava -jar
implementation
rather thantestImplementation
Resources:
java -jar
https://docs.oracle.com/en/java/javase/13/docs/specs/man/java.htmlWhen you use -jar, the specified JAR file is the source of all user classes, and other class path settings are ignored.