Skip to content

Provide annotation based-configuration for hierarchical applications/contexts #24583

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

Closed
OLibutzki opened this issue Dec 21, 2020 · 7 comments
Closed
Labels
status: duplicate A duplicate of another issue

Comments

@OLibutzki
Copy link

OLibutzki commented Dec 21, 2020

Spring Boot offers support for hierarchical application contexts by using org.springframework.boot.builder.SpringApplicationBuilder.

We use it quite a lot to build Spring based modular monoliths as it's a powerful tool to encapsulate the modules within the same Java process.

org.springframework.boot.builder.SpringApplicationBuilder works flawlessly, but this approach has its limitations when it comes to integration testing.

Imaginge you have two children which are siblings. As far as I know it's not (easily) possible to setup this scenario in an integration test. I would like to use all the great support of @SpringBootTest.

Having a annoation-based API to configure context hierarchies might be a good starting point for testing such a hierarchical application.

Not sure if this should be provided by @SpringBootApplication or a dedicated annoation, but first of all I just want to explain the idea

@SpringBootApplication(
  children = {
    @ChildApplication( name=Client1, source = Child1Configuration.class),
    @ChildApplication( name=Client2, source = Child2Configuration.class)
  }
)
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

In the integration Test I would like to use (and mock) beans of both contexts:

@SpringBootTest
public class TestingWebApplicationTests {
  @MockBean
  @ApplicationQualifier("Client1")
  private SomeBean someBean

  @Test
  public void test() {
    }
}

I know that this idea needs some fine-tuning, but I just would like to share it with you in order to get to know if it's worth to think about it any further.

Edit: If you are aware of a way how to test this setup appropriately it would be fine as well.
//cc @sbrannen

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Dec 21, 2020
@mdeinum
Copy link
Contributor

mdeinum commented Dec 22, 2020

Isn' that what the @ContextHierarchy annotation is for. From the Spring TCF?

//cc @sbrannen

@OLibutzki
Copy link
Author

With @ContextHierarchy you cannot build hierarchies with siblings and you have to "rebuild" the hierarchy in your test as you cannot use the declaration of the application.

@sbrannen
Copy link
Member

This sounds like it might be related to #22405 in terms of reusing programmatic configuration in tests.

With @ContextHierarchy you cannot build hierarchies with siblings

You can build hierarchies with siblings as long as the siblings are configured in different test classes.

@ContextHierarchy(@ContextConfiguration(classes = BaseConfig.class))
class BaseTests {}

@ContextConfiguration(classes = Child1Config.class)
class Child1Tests extends BaseTests {}

@ContextConfiguration(classes = Child2Config.class)
class Child2Tests extends BaseTests {}

The application contexts for the two subclasses are siblings since they have the same parent application context. The above results in 3 distinct application contexts in the context cache.

and you have to "rebuild" the hierarchy in your test as you cannot use the declaration of the application.

That's correct, but #22405 may help to alleviate that (unless I've misunderstood the goals of that issue).


In the integration Test I would like to use (and mock) beans of both contexts:

Are you saying you want to be able to have beans from sibling application contexts injected into the same test class instance?

If so, please expound on the use case and rationale for wanting that.

In any case, to the best of my knowledge there is no support in the Spring Framework or Spring Boot for performing dependency injection from multiple (sibling) application contexts. For example, the whole @Autowired support searches for autowire candidates within a single ApplicationContext (potentially searching in a parent ApplicationContext but not in a sibling or child ApplicationContext), and I am confident that the @MockBean support works in a similar fashion.

In other words, I don't think what you're asking for is currently possible, and to make it possible would likely require considerable effort that is not warranted by the benefit.

@OLibutzki
Copy link
Author

OLibutzki commented Dec 22, 2020

This sounds like it might be related to #22405 in terms of reusing programmatic configuration in tests.

I agree, but having a context hierarchy seems to be a bit more advanced than the use cases discussed in #22405. Nevertheless the limitation is similar.

Are you saying you want to be able to have beans from sibling application contexts injected into the same test class instance?

If so, please expound on the use case and rationale for wanting that.

Let me try to explain the motivation: As mentioned in my initial post we use hierarchical Spring applications for building modular monoliths.

Each module is represented by a child context. Therefore we do not have deep hierarchies. There is just a root context and several direct children of that root context.

Modules cannot access beans of other modules, but there are certain beans (like an EventBus) which can be used for inter-module communication.

Imagine this EventBus is a bean of the parent context and all the child contexts can use this EventBus to subscribe to events and to publish events.

A scenario I'd like to test:

  • BeanA of Context A subscribes to an event (using a @EventHandler annotation)
  • BeanB of Context B publishes the event
  • Assert that BeanA is notified of the event

Of course all of this can be tested in isolation using mocks, but I would like to have the possibility to test it in an integrated (end-to-end) manner.

Currently I write the test similar to this:

@Configuration
static class TestConfig {
    @Bean
    ChildEventHandler childEventHandler( ) {
      return mock( ChildEventHandler.class );
    }
}

static class ChildEventHandler {
  @EventHandler
  void on( final MyEvent myEvent) {
  }
}

static class MyEvent {
}

@Test
void test() {
  // Initialization
  final String[] args = new String[] { };
  final SpringApplication parentApplication = new SpringApplication( ParentConfig.class );
  final ConfigurableApplicationContext parentContext = parentApplication.run( args );
  final SpringApplication child1Application = new SpringApplication( TestConfig.class, Child1Config.class );
  final SpringApplication child2Application = new SpringApplication( TestConfig.class, Child2Config.class );
  child1Application.addInitializers( new ParentContextApplicationContextInitializer( parentContext ) );
  child2Application.addInitializers( new ParentContextApplicationContextInitializer( parentContext ) );
  final ConfigurableApplicationContext child1Context = child1Application.run( args );
  final ConfigurableApplicationContext child2Context = child2Application.run( args );

  // Test
  final ChildEventHandler child1EventHandler = child1Context.getBean( ChildEventHandler.class );
  final EventBus child2EventBus  = child2Context.getBean( EventBus .class );
  final MyEvent myEvent= new MyEvent ( );
  child2EventBus .publish( myEvent);
  verify( child1EventHandler, timeout( 3000 ) ).on( myEvent);

  // Shutdown
  child1Context.close( );
  child2Context.close( );
  parentContext.close( );
}

What happens behind the scenes:

  • Child 2 persists the event in a transaction
  • A tracker tracks the event store and notifies the event listener in another transaction

That's the reason why I use Mockito's verify with a timeout.

I hope I could clarify why and how we use hierarchical contexts. If you have any further questions feel free to ask.

OLibutzki added a commit to OLibutzki/spring-boot-24583 that referenced this issue Dec 28, 2020
@OLibutzki
Copy link
Author

OLibutzki commented Dec 28, 2020

I decided to put this issue into a working example: https://github.com/OLibutzki/spring-boot-24583

The interesting part is SpringBoot24583Test.

As you can see there is a lot manual @BeforeEach and @AfterEach setup/cleanup required.
Moreover, the hierarchy might differ from the application itself.

In my opinion the referenced repository might be a good starting point for improving the support.

@OLibutzki OLibutzki changed the title Provide an annotation based-confgiuration for hierarchical applications/contexts Provide annotation based-configuration for hierarchical applications/contexts Jan 2, 2021
@philwebb philwebb added the for: team-meeting An issue we'd like to discuss as a team to make progress label Jan 4, 2021
@philwebb
Copy link
Member

philwebb commented Jan 6, 2021

Adding additional attributes to @SpringBootApplication is likely to add quite a bit of complexity, and we'd rather keep the hierarchical support in SpringApplication.

I think the real problem discussed in this issue is that it's hard to use @SpringBootTest in combination with a customized SpringApplication. That's been raised before in #22405 and I think if we solved that issue we'd also solve your problem.

I'm going to mark this one as duplicate of #22405 since I think that's the better issue for us to focus on.

@philwebb philwebb closed this as completed Jan 6, 2021
@philwebb philwebb added status: duplicate A duplicate of another issue and removed for: team-meeting An issue we'd like to discuss as a team to make progress status: waiting-for-triage An issue we've not yet triaged labels Jan 6, 2021
@OLibutzki
Copy link
Author

Hi @philwebb,

thanks for your answer.

You are right. I'm fine with using SpringApplication. Indeed, the real problem is the testing support.

I will add a comment in #22405 just to ensure that context hierarchies are considered in the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

5 participants