Add SpringBootTest.useMainMethod support

Add a new `useMainMethod` attribute to `SpringBootTest` which can be
used to determine how the test should run. The three available options
are:

	- `ALWAYS`
	- `NEVER`
	- `WHEN_AVAILABLE`

The default is `WHEN_AVAILABLE` which will attempt to launch the test
using the `main` method if there is one.

The `SpringBootContextLoader` has been updated to use the new
`SpringApplicationHook` interface when the main method is being used.

Closes gh-22405
This commit is contained in:
Phillip Webb
2022-09-12 13:26:09 -07:00
parent fadbb4b763
commit 41e0bbf4bb
20 changed files with 684 additions and 174 deletions

View File

@@ -16,16 +16,23 @@
package org.springframework.boot.test.context;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplication.AbandonedRunException;
import org.springframework.boot.SpringApplicationHook;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;
import org.springframework.boot.test.mock.web.SpringBootMockServletContext;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.test.util.TestPropertyValues.Type;
@@ -54,7 +61,9 @@ import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.function.ThrowingSupplier;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
@@ -86,7 +95,47 @@ public class SpringBootContextLoader extends AbstractContextLoader {
@Override
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
assertHasClassesOrLocations(mergedConfig);
SpringBootTestAnnotation annotation = SpringBootTestAnnotation.get(mergedConfig);
String[] args = annotation.getArgs();
UseMainMethod useMainMethod = annotation.getUseMainMethod();
ContextLoaderHook hook = new ContextLoaderHook(mergedConfig);
if (useMainMethod != UseMainMethod.NEVER) {
Method mainMethod = getMainMethod(mergedConfig, useMainMethod);
if (mainMethod != null) {
return hook.run(() -> ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { args }));
}
}
SpringApplication application = getSpringApplication();
return hook.run(() -> application.run(args));
}
private void assertHasClassesOrLocations(MergedContextConfiguration mergedConfig) {
boolean hasClasses = !ObjectUtils.isEmpty(mergedConfig.getClasses());
boolean hasLocations = !ObjectUtils.isEmpty(mergedConfig.getLocations());
Assert.state(hasClasses || hasLocations,
() -> "No configuration classes or locations found in @SpringApplicationConfiguration. "
+ "For default configuration detection to work you need Spring 4.0.3 or better (found "
+ SpringVersion.getVersion() + ").");
}
private Method getMainMethod(MergedContextConfiguration mergedConfig, UseMainMethod useMainMethod) {
Class<?> springBootConfiguration = Arrays.stream(mergedConfig.getClasses())
.filter(this::isSpringBootConfiguration).findFirst().orElse(null);
Assert.state(springBootConfiguration != null || useMainMethod == UseMainMethod.WHEN_AVAILABLE,
"Cannot use main method as no @SpringBootConfiguration-annotated class is available");
Method mainMethod = (springBootConfiguration != null)
? ReflectionUtils.findMethod(springBootConfiguration, "main", String[].class) : null;
Assert.state(mainMethod != null || useMainMethod == UseMainMethod.WHEN_AVAILABLE,
() -> "Main method not found on '%s'".formatted(springBootConfiguration.getName()));
return mainMethod;
}
private boolean isSpringBootConfiguration(Class<?> candidate) {
return MergedAnnotations.from(candidate, SearchStrategy.TYPE_HIERARCHY)
.isPresent(SpringBootConfiguration.class);
}
private void configure(MergedContextConfiguration mergedConfig, SpringApplication application) {
application.setMainApplicationClass(mergedConfig.getTestClass());
application.addPrimarySources(Arrays.asList(mergedConfig.getClasses()));
application.getSources().addAll(Arrays.asList(mergedConfig.getLocations()));
@@ -103,7 +152,8 @@ public class SpringBootContextLoader extends AbstractContextLoader {
else {
application.setWebApplicationType(WebApplicationType.NONE);
}
application.setApplicationContextFactory((type) -> getApplicationContextFactory(mergedConfig, type));
application.setApplicationContextFactory(
(webApplicationType) -> getApplicationContextFactory(mergedConfig, webApplicationType));
application.setInitializers(initializers);
ConfigurableEnvironment environment = getEnvironment();
if (environment != null) {
@@ -113,30 +163,19 @@ public class SpringBootContextLoader extends AbstractContextLoader {
else {
application.addListeners(new PrepareEnvironmentListener(mergedConfig));
}
String[] args = SpringBootTestArgs.get(mergedConfig.getContextCustomizers());
return application.run(args);
}
private void assertHasClassesOrLocations(MergedContextConfiguration mergedConfig) {
boolean hasClasses = !ObjectUtils.isEmpty(mergedConfig.getClasses());
boolean hasLocations = !ObjectUtils.isEmpty(mergedConfig.getLocations());
Assert.state(hasClasses || hasLocations,
() -> "No configuration classes or locations found in @SpringApplicationConfiguration. "
+ "For default configuration detection to work you need Spring 4.0.3 or better (found "
+ SpringVersion.getVersion() + ").");
}
private ConfigurableApplicationContext getApplicationContextFactory(MergedContextConfiguration mergedConfig,
WebApplicationType type) {
if (type != WebApplicationType.NONE && !isEmbeddedWebEnvironment(mergedConfig)) {
if (type == WebApplicationType.REACTIVE) {
WebApplicationType webApplicationType) {
if (webApplicationType != WebApplicationType.NONE && !isEmbeddedWebEnvironment(mergedConfig)) {
if (webApplicationType == WebApplicationType.REACTIVE) {
return new GenericReactiveWebApplicationContext();
}
if (type == WebApplicationType.SERVLET) {
if (webApplicationType == WebApplicationType.SERVLET) {
return new GenericWebApplicationContext();
}
}
return ApplicationContextFactory.DEFAULT.create(type);
return ApplicationContextFactory.DEFAULT.create(webApplicationType);
}
private void prepareEnvironment(MergedContextConfiguration mergedConfig, SpringApplication application,
@@ -165,9 +204,10 @@ public class SpringBootContextLoader extends AbstractContextLoader {
}
/**
* Builds new {@link org.springframework.boot.SpringApplication} instance. You can
* override this method to add custom behavior
* @return {@link org.springframework.boot.SpringApplication} instance
* Builds new {@link org.springframework.boot.SpringApplication} instance. This method
* is only called when a {@code main} method isn't being used to create the
* {@link SpringApplication}.
* @return a {@link SpringApplication} instance
*/
protected SpringApplication getSpringApplication() {
return new SpringApplication();
@@ -215,16 +255,14 @@ public class SpringBootContextLoader extends AbstractContextLoader {
initializers.add(BeanUtils.instantiateClass(initializerClass));
}
if (mergedConfig.getParent() != null) {
initializers
.add(new ParentContextApplicationContextInitializer(mergedConfig.getParentApplicationContext()));
ApplicationContext parentApplicationContext = mergedConfig.getParentApplicationContext();
initializers.add(new ParentContextApplicationContextInitializer(parentApplicationContext));
}
return initializers;
}
private boolean isEmbeddedWebEnvironment(MergedContextConfiguration mergedConfig) {
return MergedAnnotations.from(mergedConfig.getTestClass(), SearchStrategy.TYPE_HIERARCHY)
.get(SpringBootTest.class).getValue("webEnvironment", WebEnvironment.class).orElse(WebEnvironment.NONE)
.isEmbedded();
return SpringBootTestAnnotation.get(mergedConfig).getWebEnvironment().isEmbedded();
}
@Override
@@ -371,4 +409,45 @@ public class SpringBootContextLoader extends AbstractContextLoader {
}
/**
* {@link SpringApplicationHook} used to capture the {@link ApplicationContext} and to
* trigger early exit for the {@link Mode#AOT_PROCESSING} mode.
*/
private class ContextLoaderHook implements SpringApplicationHook {
private final MergedContextConfiguration mergedConfig;
ContextLoaderHook(MergedContextConfiguration mergedConfig) {
this.mergedConfig = mergedConfig;
}
@Override
public SpringApplicationRunListener getRunListener(SpringApplication application) {
return new SpringApplicationRunListener() {
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
SpringBootContextLoader.this.configure(ContextLoaderHook.this.mergedConfig, application);
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
throw new AbandonedRunException(context);
}
};
}
private <T> ApplicationContext run(ThrowingSupplier<T> action) {
try {
SpringApplication.withHook(this, action);
throw new IllegalStateException("ApplicationContext not loaded");
}
catch (AbandonedRunException ex) {
return ex.getApplicationContext();
}
}
}
}

View File

@@ -125,6 +125,14 @@ public @interface SpringBootTest {
*/
WebEnvironment webEnvironment() default WebEnvironment.MOCK;
/**
* The type of main method usage to employ when creating the {@link SpringApplication}
* under test.
* @return the type of main method usage
* @since 3.0.0
*/
UseMainMethod useMainMethod() default UseMainMethod.WHEN_AVAILABLE;
/**
* An enumeration web environment modes.
*/
@@ -175,4 +183,34 @@ public @interface SpringBootTest {
}
/**
* Enumeration of how the main method of the
* {@link SpringBootConfiguration @SpringBootConfiguration}-annotated class is used
* when creating and running the {@link SpringApplication} under test.
*/
enum UseMainMethod {
/**
* Always use the {@code main} method. A failure will occur if there is no
* {@link SpringBootConfiguration @SpringBootConfiguration}-annotated class or
* that class does not have a main method.
*/
ALWAYS,
/**
* Never use the {@code main} method, creating a test-specific
* {@link SpringApplication} instead.
*/
NEVER,
/**
* Use the {@code main} method when it is available. If there is no
* {@link SpringBootConfiguration @SpringBootConfiguration}-annotated class or
* that class does not have a main method, a test-specific
* {@link SpringApplication} will be used.
*/
WHEN_AVAILABLE;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.context;
import java.util.Arrays;
import java.util.Objects;
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
/**
* {@link ContextCustomizer} to track attributes of
* {@link SpringBootTest @SptringBootTest} that are taken into account when evaluating a
* {@link MergedContextConfiguration} to determine if a context can be shared between
* tests.
*
* @author Phillip Webb
* @author Madhura Bhave
* @author Andy Wilkinson
*/
class SpringBootTestAnnotation implements ContextCustomizer {
private static final String[] NO_ARGS = new String[0];
private static final SpringBootTestAnnotation DEFAULT = new SpringBootTestAnnotation((SpringBootTest) null);
private final String[] args;
private final WebEnvironment webEnvironment;
private final UseMainMethod useMainMethod;
SpringBootTestAnnotation(Class<?> testClass) {
this(TestContextAnnotationUtils.findMergedAnnotation(testClass, SpringBootTest.class));
}
private SpringBootTestAnnotation(SpringBootTest annotation) {
this.args = (annotation != null) ? annotation.args() : NO_ARGS;
this.webEnvironment = (annotation != null) ? annotation.webEnvironment() : WebEnvironment.NONE;
this.useMainMethod = (annotation != null) ? annotation.useMainMethod() : UseMainMethod.WHEN_AVAILABLE;
}
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
SpringBootTestAnnotation other = (SpringBootTestAnnotation) obj;
boolean result = Arrays.equals(this.args, other.args);
result = result && this.useMainMethod == other.useMainMethod;
result = result && this.webEnvironment == other.webEnvironment;
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(this.args);
result = prime * result + Objects.hash(this.useMainMethod, this.webEnvironment);
return result;
}
String[] getArgs() {
return this.args;
}
WebEnvironment getWebEnvironment() {
return this.webEnvironment;
}
UseMainMethod getUseMainMethod() {
return this.useMainMethod;
}
/**
* Return the application arguments from the given {@link MergedContextConfiguration}.
* @param mergedConfig the merged config to check
* @return a {@link SpringBootTestAnnotation} instance
*/
static SpringBootTestAnnotation get(MergedContextConfiguration mergedConfig) {
for (ContextCustomizer customizer : mergedConfig.getContextCustomizers()) {
if (customizer instanceof SpringBootTestAnnotation annotation) {
return annotation;
}
}
return DEFAULT;
}
}

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.context;
import java.util.Arrays;
import java.util.Set;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
/**
* {@link ContextCustomizer} to track application arguments that are used in a
* {@link SpringBootTest}. The application arguments are taken into account when
* evaluating a {@link MergedContextConfiguration} to determine if a context can be shared
* between tests.
*
* @author Madhura Bhave
*/
class SpringBootTestArgs implements ContextCustomizer {
private static final String[] NO_ARGS = new String[0];
private final String[] args;
SpringBootTestArgs(Class<?> testClass) {
this.args = MergedAnnotations.from(testClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.get(SpringBootTest.class).getValue("args", String[].class).orElse(NO_ARGS);
}
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
}
String[] getArgs() {
return this.args;
}
@Override
public boolean equals(Object obj) {
return (obj != null) && (getClass() == obj.getClass())
&& Arrays.equals(this.args, ((SpringBootTestArgs) obj).args);
}
@Override
public int hashCode() {
return Arrays.hashCode(this.args);
}
/**
* Return the application arguments from the given customizers.
* @param customizers the customizers to check
* @return the application args or an empty array
*/
static String[] get(Set<ContextCustomizer> customizers) {
for (ContextCustomizer customizer : customizers) {
if (customizer instanceof SpringBootTestArgs testArgs) {
return testArgs.args;
}
}
return NO_ARGS;
}
}

View File

@@ -362,8 +362,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
protected final MergedContextConfiguration createModifiedConfig(MergedContextConfiguration mergedConfig,
Class<?>[] classes, String[] propertySourceProperties) {
Set<ContextCustomizer> contextCustomizers = new LinkedHashSet<>(mergedConfig.getContextCustomizers());
contextCustomizers.add(new SpringBootTestArgs(mergedConfig.getTestClass()));
contextCustomizers.add(new SpringBootTestWebEnvironment(mergedConfig.getTestClass()));
contextCustomizers.add(new SpringBootTestAnnotation(mergedConfig.getTestClass()));
return new MergedContextConfiguration(mergedConfig.getTestClass(), mergedConfig.getLocations(), classes,
mergedConfig.getContextInitializerClasses(), mergedConfig.getActiveProfiles(),
mergedConfig.getPropertySourceLocations(), propertySourceProperties, contextCustomizers,

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.context;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
/**
* {@link ContextCustomizer} to track the web environment that is used in a
* {@link SpringBootTest}. The web environment is taken into account when evaluating a
* {@link MergedContextConfiguration} to determine if a context can be shared between
* tests.
*
* @author Andy Wilkinson
*/
class SpringBootTestWebEnvironment implements ContextCustomizer {
private final WebEnvironment webEnvironment;
SpringBootTestWebEnvironment(Class<?> testClass) {
SpringBootTest sprintBootTest = TestContextAnnotationUtils.findMergedAnnotation(testClass,
SpringBootTest.class);
this.webEnvironment = (sprintBootTest != null) ? sprintBootTest.webEnvironment() : null;
}
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
}
@Override
public boolean equals(Object obj) {
return (obj != null) && (getClass() == obj.getClass())
&& this.webEnvironment == ((SpringBootTestWebEnvironment) obj).webEnvironment;
}
@Override
public int hashCode() {
return (this.webEnvironment != null) ? this.webEnvironment.hashCode() : 0;
}
}

View File

@@ -23,6 +23,9 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.context.ApplicationContext;
@@ -40,6 +43,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link SpringBootContextLoader}
@@ -114,8 +118,7 @@ class SpringBootContextLoaderTests {
assertThat(getActiveProfiles(ActiveProfileWithComma.class)).containsExactly("profile1,2");
}
@Test
// gh-28776
@Test // gh-28776
void testPropertyValuesShouldTakePrecedenceWhenInlinedPropertiesPresent() {
TestContext context = new ExposedTestContextManager(SimpleConfig.class).getExposedTestContext();
StandardEnvironment environment = (StandardEnvironment) context.getApplicationContext().getEnvironment();
@@ -162,6 +165,37 @@ class SpringBootContextLoaderTests {
assertThat(context.getApplicationContext()).isInstanceOf(GenericReactiveWebApplicationContext.class);
}
@Test
void whenUseMainMethodAlwaysAndMainMethodThrowsException() {
TestContext testContext = new ExposedTestContextManager(UseMainMethodAlwaysAndMainMethodThrowsException.class)
.getExposedTestContext();
assertThatIllegalStateException().isThrownBy(testContext::getApplicationContext).havingCause()
.withMessageContaining("ThrownFromMain");
}
@Test
void whenUseMainMethodWhenAvailableAndNoMainMethod() {
TestContext testContext = new ExposedTestContextManager(UseMainMethodWhenAvailableAndNoMainMethod.class)
.getExposedTestContext();
ApplicationContext applicationContext = testContext.getApplicationContext();
assertThat(applicationContext.getEnvironment().getActiveProfiles()).isEmpty();
}
@Test
void whenUseMainMethodWhenAvailableAndMainMethod() {
TestContext testContext = new ExposedTestContextManager(UseMainMethodWhenAvailableAndMainMethod.class)
.getExposedTestContext();
ApplicationContext applicationContext = testContext.getApplicationContext();
assertThat(applicationContext.getEnvironment().getActiveProfiles()).contains("frommain");
}
@Test
void whenUseMainMethodNever() {
TestContext testContext = new ExposedTestContextManager(UseMainMethodNever.class).getExposedTestContext();
ApplicationContext applicationContext = testContext.getApplicationContext();
assertThat(applicationContext.getEnvironment().getActiveProfiles()).isEmpty();
}
private String[] getActiveProfiles(Class<?> testClass) {
TestContext testContext = new ExposedTestContextManager(testClass).getExposedTestContext();
ApplicationContext applicationContext = testContext.getApplicationContext();
@@ -239,11 +273,6 @@ class SpringBootContextLoaderTests {
}
@Configuration(proxyBeanMethods = false)
static class Config {
}
@SpringBootTest(classes = Config.class, args = "--spring.main.web-application-type=none")
static class ChangingWebApplicationTypeToNone {
@@ -254,6 +283,57 @@ class SpringBootContextLoaderTests {
}
@SpringBootTest(classes = ConfigWithThrowingMain.class, useMainMethod = UseMainMethod.ALWAYS)
static class UseMainMethodAlwaysAndMainMethodThrowsException {
}
@SpringBootTest(classes = ConfigWithNoMain.class, useMainMethod = UseMainMethod.WHEN_AVAILABLE)
static class UseMainMethodWhenAvailableAndNoMainMethod {
}
@SpringBootTest(classes = ConfigWithMain.class, useMainMethod = UseMainMethod.WHEN_AVAILABLE)
static class UseMainMethodWhenAvailableAndMainMethod {
}
@SpringBootTest(classes = ConfigWithMain.class, useMainMethod = UseMainMethod.NEVER)
static class UseMainMethodNever {
}
@Configuration(proxyBeanMethods = false)
static class Config {
}
@Configuration(proxyBeanMethods = false)
@SpringBootConfiguration
public static class ConfigWithMain {
public static void main(String[] args) {
new SpringApplication(ConfigWithMain.class).run("--spring.profiles.active=frommain");
}
}
@Configuration(proxyBeanMethods = false)
@SpringBootConfiguration
static class ConfigWithNoMain {
}
@Configuration(proxyBeanMethods = false)
@SpringBootConfiguration
public static class ConfigWithThrowingMain {
public static void main(String[] args) {
throw new RuntimeException("ThrownFromMain");
}
}
/**
* {@link TestContextManager} which exposes the {@link TestContext}.
*/