Skip to content

Allow proper replacement of @SpringBootTest and extension of SpringBootTestContextBootstrapper #15077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
erezwncare opened this issue Nov 2, 2018 · 11 comments
Labels
theme: testing Issues related to testing type: enhancement A general enhancement
Milestone

Comments

@erezwncare
Copy link

erezwncare commented Nov 2, 2018

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.

@erezwncare
Copy link
Author

erezwncare commented Nov 5, 2018

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.

@philwebb
Copy link
Member

philwebb commented Nov 5, 2018

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

@philwebb
Copy link
Member

philwebb commented Nov 5, 2018

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.

@philwebb philwebb added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Nov 5, 2018
@philwebb philwebb added this to the 2.x milestone Nov 5, 2018
@philwebb philwebb added the theme: testing Issues related to testing label Nov 5, 2018
@dreis2211
Copy link
Contributor

dreis2211 commented Nov 6, 2018

@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?

@erezwncare
Copy link
Author

erezwncare commented Nov 6, 2018

@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}) )

@philwebb
Copy link
Member

philwebb commented Nov 6, 2018

@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.

@erezwncare
Copy link
Author

erezwncare commented Jan 4, 2019

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.

@rosencreuz
Copy link

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?

@rupebac
Copy link

rupebac commented Sep 23, 2020

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.

@DrongoX
Copy link

DrongoX commented Feb 2, 2021

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.

@philwebb philwebb modified the milestones: 2.x, 3.x Aug 19, 2022
@u3r
Copy link

u3r commented Feb 2, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: testing Issues related to testing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

8 participants