How to exclude *AutoConfiguration classes in Spring Boot JUnit tests?

I tried:

@RunWith(SpringJUnit4ClassRunner.class)
@EnableAutoConfiguration(exclude=CrshAutoConfiguration.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class LikeControllerTest {

However the CRaSSHD still starts up. While currently it doesn't harm the test, I'd like to disable unnecessary modules during unit testing to speed up and also avoid potential conflicts.

166259 次浏览

I think that using the @EnableAutoConfiguration annotation on a test class won't work if you are using @SpringApplicationConfiguration to load your Application class. The thing is that you already have a @EnableAutoConfiguration annotation in the Application class that does not exclude the CrshAutoConfiguration.Spring uses that annotation instead of the one on your test class to do the auto configuration of your beans.

I think that your best bet is to use a different application context for your tests and exclude the CrshAutoConfiguration in that class.

I did some tests and it seems that @EnableAutoConfiguration on the test class is completely ignore if you are using the @SpringApplicationConfiguration annotation and the SpringJUnit4ClassRunner.

I had a similar use case where I wanted to test a Spring Boot configured repository in isolation (in my case without Spring Security autoconfiguration which was failing my test). @SpringApplicationConfiguration uses SpringApplicationContextLoader and that has a JavaDoc stating

Can be used to test non-web features (like a repository layer) or start an fully-configured embedded servlet container.

However, like yourself, I could not work out how you are meant to configure the test to only test the repository layer using the main configuration entry point i.e. using your approach of @SpringApplicationConfiguration(classes = Application.class).

My solution was to create a completely new application context exclusive for testing. So in src/test/java I have two files in a sub-package called repo

  1. RepoIntegrationTest.java
  2. TestRepoConfig.java

where RepoIntegrationTest.java has

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestRepoConfig.class)
public class RepoIntegrationTest {

and TestRepoConfig.java has

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class TestRepoConfig {

It got me out of trouble but it would be really useful if anyone from the Spring Boot team could provide an alternative recommended solution

got into same kind of problem, wasn't able to exclude main spring boot class during testing. Solved it using following approach.

Instead of using @SpringBootApplication, use all three annotations which it contains and assign the name to @Configuration

@Configuration("myApp")
@EnableAutoConfiguration
@ComponentScan
public class MyApp { .. }

In your test class define configuration with exactly same name:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
// ugly hack how to exclude main configuration
@Configuration("myApp")
@SpringApplicationConfiguration(classes = MyTest.class)
public class MyTest { ... }

This should help. Would be nice to have some better way in place how to disable auto scanning for configuration annotations...

I struggled with this as well and found a simple pattern to isolate the test context after a cursory read of the @ComponentScan docs.

/**
* Type-safe alternative to {@link #basePackages} for specifying the packages
* to scan for annotated components. The package of each class specified will be scanned.
* Consider creating a special no-op marker class or interface in each package
* that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};

  1. Create a package for your spring tests, ("com.example.test").
  2. Create a marker interface in the package as a context qualifier.
  3. Provide the marker interface reference as a parameter to basePackageClasses.

Example


IsolatedTest.java

package com.example.test;


@RunWith(SpringJUnit4ClassRunner.class)
@ComponentScan(basePackageClasses = {TestDomain.class})
@SpringApplicationConfiguration(classes = IsolatedTest.Config.class)
public class IsolatedTest {


String expected = "Read the documentation on @ComponentScan";
String actual = "Too lazy when I can just search on Stack Overflow.";


@Test
public void testSomething() throws Exception {
assertEquals(expected, actual);
}


@ComponentScan(basePackageClasses = {TestDomain.class})
public static class Config {
public static void main(String[] args) {
SpringApplication.run(Config.class, args);
}
}
}


...

TestDomain.java

package com.example.test;


public interface TestDomain {
//noop marker
}

I had a similar problem but I came to a different solution that may help others. I used Spring Profiles to separate out test and app configuration classes.

  1. Create a TestConfig class with a specific profile and exclude any app configuration from component scan you wish here.

  2. In your test class set the profile to match the TestConfig and include it using the @ContextConfiguration annotation.

For example:

configuration:

@Profile("test")
@Configuration
@EnableWebMvc
@ComponentScan(
basePackages="your.base.package",
excludeFilters = {
@Filter(type = ASSIGNABLE_TYPE,
value = {
ExcludedAppConfig1.class,
ExcludedAppConfig2.class
})
})
public class TestConfig { ...}

test:

@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@WebAppConfiguration
public class SomeTest{ ... }

Another simple way to exclude the auto configuration classes,

Add below similar configuration to your application.yml file,

---
spring:
profiles: test
autoconfigure.exclude: org.springframework.boot.autoconfigure.session.SessionAutoConfiguration

With the new @SpringBootTest annotation, I took this answer and modified it to use profiles with a @SpringBootApplication configuration class. The @Profile annotation is necessary so that this class is only picked up during the specific integration tests that need this, as other test configurations do different component scanning.

Here is the configuration class:

@Profile("specific-profile")
@SpringBootApplication(scanBasePackages={"com.myco.package1", "com.myco.package2"})
public class SpecificTestConfig {


}

Then, the test class references this configuration class:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { SpecificTestConfig.class })
@ActiveProfiles({"specific-profile"})
public class MyTest {


}

If you're having this problem with Spring Boot 1.4.x and up, you might be able to use @OverrideAutoConfiguration(enabled=true) to solve the problem.

Similar to what was asked/answered here https://stackoverflow.com/a/39253304/1410035

@SpringBootTest(classes = {Application.class}
, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
, properties="spring.autoconfigure.exclude=com.xx.xx.AutoConfiguration"
)

ref:https://github.com/spring-projects/spring-boot/issues/8579

If the issue is that your SpringBootApplication/Configuration you are bringing in is component scanning the package your test configurations are in, you can actually remove the @Configuration annotation from the test configurations and you can still use them in the @SpringBootTest annotations. For example, if you have a class Application that is your main configuration and a class TestConfiguration that is a configuration for certain, but not all tests, you can set up your classes as follows:

@Import(Application.class) //or the specific configurations you want
//(Optional) Other Annotations that will not trigger an autowire
public class TestConfiguration {
//your custom test configuration
}

And then you can configure your tests in one of two ways:

  1. With the regular configuration:

    @SpringBootTest(classes = {Application.class}) //won't component scan your configuration because it doesn't have an autowire-able annotation
    //Other annotations here
    public class TestThatUsesNormalApplication {
    //my test code
    }
    
  2. With the test custom test configuration:

    @SpringBootTest(classes = {TestConfiguration.class}) //this still works!
    //Other annotations here
    public class TestThatUsesCustomTestConfiguration {
    //my test code
    }
    

I have struggled with a similar issue for one day... My Scenario:

I have a SpringBoot application and I use applicationContext.xml in scr/main/resources to configure all my Spring Beans. For testing(integration testing) I use another applicationContext.xml in test/resources and things worked as I have expected: Spring/SpringBoot would override applicationContext.xml from scr/main/resources and would use the one for Testing which contained the beans configured for testing.

However, just for one UnitTest I wanted yet another customization for the applicationContext.xml used in Testing, just for this Test I wanted to used some mockito beans, so I could mock and verify, and here started my one day head-ache!

The problem is that Spring/SpringBoot doesn't not override the applicationContext.xml from scr/main/resources ONLY IF the file from test/resources HAS the SAME NAME. I tried for hours to use something like:

@RunWith(SpringJUnit4ClassRunner.class)
@OverrideAutoConfiguration(enabled=true)
@ContextConfiguration({"classpath:applicationContext-test.xml"})

it did not work, Spring was first loading the beans from applicationContext.xml in scr/main/resources

My solution based on the answers here by @myroch and @Stuart:

  1. Define the main configuration of the application:

    @Configuration @ImportResource({"classpath:applicationContext.xml"}) public class MainAppConfig { }

this is used in the application

@SpringBootApplication
@Import(MainAppConfig.class)
public class SuppressionMain implements CommandLineRunner
  1. Define a TestConfiguration for the Test where you want to exclude the main configuration

    @ComponentScan( basePackages = "com.mypackage", excludeFilters = { @ComponentScan.Filter(type = ASSIGNABLE_TYPE, value = {MainAppConfig.class}) }) @EnableAutoConfiguration public class TestConfig { }

By doing this, for this Test, Spring will not load applicationContext.xml and will load only the custom configuration specific for this Test.

Top answers don't point to an even simpler and more flexible solution.

just place a

@TestPropertySource(properties=
{"spring.autoconfigure.exclude=comma.seperated.ClassNames,com.example.FooAutoConfiguration"})
@SpringBootTest
public class MySpringTest {...}

annotation above your test class. This means other tests aren't affected by the current test's special case. If there is a configuration affecting most of your tests, then consider using the spring profile instead as the current top answer suggests.

Thanks to @skirsch for encouraging me to upgrade this from a comment to an answer.

I think that the best solution currently for springBoot 2.0 is using profiles

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.DEFINED_PORT)
@ActiveProfiles("test")
public class ExcludeAutoConfigIntegrationTest {
// ...
}

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

anyway in the following link give 6 different alternatives to solve this.

So to disable the auto-loading of all Beans for a Test, the test class can explicitly mention the dependencies required. This can be done using ContextConfiguration annotation. eg,

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {EmployeeService.class})
public class EmployeeLeavesTest {


@Autowired
private EmployeeService employeeService;


}

In this eg, only EmployeeService class will be available and other beans will not be loaded.