Spring-boot: Exclude TestConfiguration classes even when running outside a spring-test managed context

Created on 29 Nov 2016  路  14Comments  路  Source: spring-projects/spring-boot

Spinoff of https://jira.spring.io/browse/SPR-9090

IDEs such as Eclipse commingle /src/test/java and /src/main/java into a single classpath. As such, @TestConfiguration classes can be picked up when running the main SpringBootApplication in Eclipse.

Spring Boot Test already has the capability to exclude such configuration classes. Unfortunately, that capability uses the ContextCustomizer hook which is only available inside a spring-test-managed context. Would it be possible to migrate this capability into the core framework such that the TypeExcludeFilters could be leveraged when running a main application?

declined

Most helpful comment

For future reference for anyone reading this thread, here is the buildship bug for separating runtime and test classpaths.

All 14 comments

IDEs such as Eclipse commingle /src/test/java and /src/main/java into a single classpath. As such, @TestConfiguration classes can be picked up when running the main SpringBootApplication in Eclipse

This doesn't appear to happen for me. If I add the following to our simple sample application in src/main/test:

@Configuration
public class TestConfig {

    @PostConstruct
    public void bang() {
        throw new RuntimeException("BOOM!");
    }

}

It loads just fine. Furthermore, if I refer to TestConfig from any code in src/main/java I get:

Exception in thread "main" java.lang.NoClassDefFoundError: sample/simple/TestConfig
    at sample.simple.SampleSimpleApplication.main(SampleSimpleApplication.java:45)
Caused by: java.lang.ClassNotFoundException: sample.simple.TestConfig
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 1 more

@sirianni Do you have an example application we can look at, along with the steps needed to trigger this behavior in Eclipse?

To be clear, this issue applies to running tests and main apps via Eclipse only.

Are you using any special Eclipse plugins? Eclipse uses a single classpath for all launch targets within a project and does not differentiate main vs. test.

See:

To be clear, this issue applies to running tests and main apps via Eclipse only.

I'm using Eclipse

Are you using any special Eclipse plugins?

No, but perhaps m2e is being smart when tests are launched. What build system do you use? Have you got a small example that we can run?

I am using gradle to generate eclipse project/classpath files (no plugins in eclipse itself).
I will create a small example project tomorrow.

perhaps m2e is being smart when tests are launched

It does appear that m2e is doing something intelligent here to workaround the (wrong) Eclipse behavior.

From https://bugs.eclipse.org/bugs/show_bug.cgi?id=410228

Here is how m2e decides if java application launch should use test or runtime artifact scopes.

  • "run as junit test" and "run as testng test" uses test scope.
  • "run as java application" uses test scope if application main class is located under src/test/java, otherwise uses runtime scope.

@sirianni That explains it, thanks. The sample I was running was Maven.

After some consideration we've decided it's not a good idea for us to try and compensate for the shortcomings of the IDE. It might be worth investigating buildship Gradle support to see if that has more smarts about running tests with the correct classpath.

After some consideration we've decided it's not a good idea for us to try and compensate for the shortcomings of the IDE.

Thanks for the consideration and quick feedback. I can't say I disagree with your decision.

For future reference for anyone reading this thread, here is the buildship bug for separating runtime and test classpaths.

And the Eclipse bug for supporting multiple classpaths per project.

Sorry to be replying to a dead thread, but is there a way to use TypeExcludeFilters outside of a Spring Test scenario? All of the code that detects and initializes them seems coupled to the spring-test framework (ContextCustomizer, etc.).

Is there a way to activate TypeExcludeFilters in a normal (non-test) Spring Boot application context?

@sirianni See org.springframework.boot.context.TypeExcludeFilter

Here's an example that uses an ApplicationContextInitializer to add a filter that prints out all classes (but doesn't actually filter):

    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication(SampleSimpleApplication.class);
        app.addInitializers(
                new ApplicationContextInitializer<ConfigurableApplicationContext>() {

                    @Override
                    public void initialize(
                            ConfigurableApplicationContext applicationContext) {
                        applicationContext.getBeanFactory().registerSingleton(
                                "mytypefilter", new TypeExcludeFilter() {

                            @Override
                            public boolean match(MetadataReader metadataReader,
                                    MetadataReaderFactory metadataReaderFactory)
                                            throws IOException {
                                System.err.println(
                                        metadataReader.getClassMetadata().getClassName());
                                return false;
                            }

                        });
                    }

                });
        app.run(args);
    }

Thanks @philwebb . The usage of ApplicationContextInitializer was the piece I was missing as all of the internal framework usage was via ContextCustomizer.

So all TypeExcludeFilter singletons need to get explicitly registered? Is there a way for TypeExcludeFilters themselves to get picked up via @ComponentScan in some sort of "2 pass" initialization (i.e. with a static @Bean method similar to property placeholder stuff)?

So all TypeExcludeFilter singletons need to get explicitly registered? Is there a way for TypeExcludeFilters themselves to get picked up via @ComponentScan in some sort of "2 pass" initialization

No, I'm afraid not. You can however register ApplicationContextInitializer in the spring.factories file which might help if you wanted to try an automatically apply filters in a bunch of projects.

The pattern I use to get around Eclipse is for any config classes I create for my tests I tag them with @Profile("NameOfMyTestConfiguration"), and then any test classes which use them I tag with @ActiveProfiles("NameOfMyTestConfiguration"). This ensures that when running my tests only my relevant test classes get picked up, and when running my application none of my test class configuration gets picked up.

The other trick I use is to always run my application in Eclipse using the Gradle Buildship plugin bootRun task. Gradle will make sure only the correct classes will be picked up.

Or you could switch to IntelliJ (which I've done over the past few months), then you don't need to worry at all about it!

Was this page helpful?
0 / 5 - 0 ratings