The last version of SpringBootTestContextBootstrapper allows extension so users could create there own test bootstrappers while keeping Spring Test Context creation in line with default implementation.

Since @BootstrapWith annotation can only be present once, any test that want to use the new bootstrap needs to replace , and can not use, @SpringBootTest annotation.

Once a new annotation is created, the definition may look like this :

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(ExtSpringBootTestContextBootstrapper.class)
public @interface ExtSpringBootTest {}

The ExtSpringBootTestContextBootstrapper will have not issues functioning correctly with lack of @SpringBootTest since the only method that reads it is org.springframework.boot.test.context.SpringBootTestContextBootstrapper#getAnnotation(java.lang.Class<?>) and this method can be overridden by ExtSpringBootTestContextBootstrapper to search for @ExtSpringBootTest.

However, this is not the case with SpringBootContextLoader. The @SpringBootTest annotation is reference in a few places, mostly in order to get the .isEmbedded property. This require any ExtSpringBootContextLoader to override more then one method, and copy paste some code... Also, @SpringBootTest is reference in TestRestTemplateContextCustomizer and WebTestClientContextCustomizer.

Its a shame that is is now almost possible to easily extend the bootstrap... the few small changes suggested here could complete this ability.

Comment From: erezwncare

I think in general @SpringBootTest does not provide enough flexibility, and that is why I opened this ticket. There are many tweeks and annotations that I may want to support in junit\integration tests that have a spring context:

  • @ComponentScan with full flexibility (excludeClassesPackage and more...)
  • @PropertySource that can actually effect the context creation (for example add spring.autoconfiguration.exclude or management.server.port=-1) or creating my own @DisableEmbeddedMongo and @LoggingApplicationListenerTracker (test console output by tweeking logback logging system config before and after logging context reset) etc.

In general : Managing Application env before TestContext is created from configuration.

All these things can't really be achieved when one tries to @SpringBootTest a full application that is already well defined :

@SpringBootApplication
@CustomFrameworkAnnotationsHere ( that runs the whole show...)
class Application{

}
@SprintBootTest(class="Application.class")
@CantRealyDoMuchHereOnceContextIsFullyCreated :(
class ApplicationIT{

}

The small tweek that can allow TestContext`AppContextfull manipulation , as I see it, is by extending the bootstrap. Any small functionality addition to@SpringBootTest(like exclude=) would only scratch the surface of what a full proper IntegrationTest wants to be able to do to a@SpringBootApplication`.

Note :

  1. @ContextConfiguration(initilazer=...) can solve some of these by adding ContextInitilazers that can add PropertySources, ApplicationListeners etc. But this annotation itself is limited to 1 occurrence. A true flexible integration test should be able to compose the SpringApplication and TestContext - more like the SpringApplicationBuilder, which seems that is not supported in the TestContext.
  2. Adding factories to spring.factories - But I really don't want to start editing text files...

Currently, I successfully extended SpringBootTestContextBootstrapper so it'll search for custom annotations and create the MergedContextConfiguration from that. But it'll break if any spring class will search for the non-existing @SpringBootTest.

Comment From: philwebb

@erezwncare I've edited your comment to improve the formatting. You might want to check out this Mastering Markdown guide for future reference.

Comment From: philwebb

I wonder if we can create something similar to @ContextConfiguration from org.springframework.test.context. If we had a @SpringBootTestConfiguration that contained the relevant attributes we could change @SpringBootTest so that it is meta-annotated with @SpringBootTestConfiguration and just adds the @BootstrapWith.

Another option might be to try and decompose @SpringBootTest into multiple distinct annotations or reuse the existing @ContextConfiguration and @TestPropertySource annotations.

It would certainly be nice if we could replace the existing DataJdbcTestContextBootstrapper, DataLdapTestContextBootstrapper, DataMongoTestContextBootstrapper etc bootstrap subclasses and instead just use meta-annotations on the slice annotations.

Comment From: dreis2211

@philwebb As part of my work on #14981 I already got rid of DataJdbcTestContextBootstrapper etc. See this experimental branch. Maybe we can combine some of these efforts?

Comment From: erezwncare

@philwebb thank you, will follow this format. In my continues effort to hack the bootstrap I wanted an ApplicationListener that will be called with at least Application starting or ApplicationEnvPrepared event. These events go to the LoggingSystem since they are preconfigured from the factories, but can't currently be listen to by the integration test easily.

Once the ExtBootstrap returns ExtContextLoader, I can override getSpringApplication() and add ApplicationListener<?> to it right after new SpringApplication() - calling addListeners(). These listeners are there on Application creation so are being called with all events.

The total code written is not significant so I think some Spring tweaks can allow this to be easily accessible. The key ingredients are ContextCustomizers and ApplicationListeners. Another option would be to go to each place where the factories are loaded and add a mechanism to append factories programmatically (with new custom annotations or maybe by @SpringBootTest(factories=@Factory{type=ApplicationListeners.class,classes=UserListenerFactory.class}) )

Comment From: philwebb

@dreis2211 Good point. It was a bit late for 2.1 but it would be very worthwhile revisiting this when we branch for 2.2.

Comment From: erezwncare

Adding to the request :

Each time a code checks for the existence of SpringBootTest.class it renders this annotation as unextendable. For example in : org.springframework.boot.test.context.SpringBootContextLoader#isEmbeddedWebEnvironment

If I want to have a custom @ExtSpringBootTest, I need to override that method. But its a private method... So now I have to override all method calling it : getInlinedProperties, loadContext. I am basically copy-pasting there code to my ExtSpringBootContextLoader just to support my custom boot chain. That can be improved by making methods as protected or providing a central place for defining which is the actual SpringBootTest annotation class.

Comment From: rosencreuz

I was extending SpringBootTestContextBootstrapper to process my custom test annotations in the processMergedContextConfiguration step. This was all working fine until v2.2. Now I'm getting an error telling I cannot have two bootstrappers, the other one is coming from the @SpringBootTest which I was extending with my custom annotation. Now if I try to remove @SpringBootTest, I get all the same problems described above by @erezwncare.

As I understand this issue didn't get enough priority but is there a workaround I can do for processing my custom annotations before the context initialization is done?

Comment From: rupebac

Facing similar issues as the others. As a workaround, we will have to add BootstrapWith(AnyClassExtendingSpringBootTestContextBootstrapper.class) to all test classes, not just in the abstract one. Then it will be overriden.

In general I think the problem is that too many components are bound to this annotation type (SpringBootTest): SpringBootTestContextBootstrapper, to WebMergedContextConfiguration, WebTestClient, WebTestClientContextCustomizer... Why can't I use WebTestClientContextCustomizer without SpringBootTest ?

At first I saw so much magic happening and I did not like and tried to get rid of this SpringBootTest. But then I saw this was involved in so many classes that thought it will be even worse in the future, and be a core part of spring-test, so I will work around this.

Comment From: DrongoX

Hello. Having same issue as the original poster. We have here few custom annotations with custom SpringBootTestContextBootstrappers, and it is fine. As the original poster said, the problem is in SpringBootContextLoader. Just simply making the method #isEmbeddedWebEnvironment protected would be a great improvement for us. Thanks.

Comment From: u3r

Hi, I have arrived here hitting the same problem. Originally I just wanted to replace the org.springframework.test.context.cache.ContextCache to get some information on which classes drive up the context-count. Even though that interface designates itself as an SPI, I have not found a way of injecting a different implementation. And even providing a new SpringBootTestContextBootstrapper (which does not work for me due to the problems mentioned above) does not allow replacement, only wrapping.