I work on a library that has (1) its own lifecycle management and (2) a Spring Boot integration. To merge the library's lifecycle management with Spring Boot, I construct SmartLifecycle
beans that wrap the library's start-up and shutdown handlers.
This solution works wonders in practice, but I am hitting a snag during testing.
Validating that my start-up handlers are invoked in a Spring Boot test works fine. However, validating whether the registered shutdown handlers are invoked is unclear to me.
The test in question constructs a ApplicationContextRunner
to which I register the library's autoconfiguration classes, as well as a test configuration that includes a shutdown handler. I hope that invoking stop()
on the AssertableApplicationContext
would do the trick, but it does not. To be a bit more concrete, here's a sample of what it looks like:
@Test
void lifecycleRegistryIntegratesWithDedicatedSpringLifecycleBeans() {
testContext.run(context -> {
// We expect beans to be registered for lifecycle handlers.
Map<String, SpringLifecycleStartHandler> startHandlers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, SpringLifecycleStartHandler.class
);
Map<String, SpringLifecycleShutdownHandler> shutdownHandlers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, SpringLifecycleShutdownHandler.class
);
for (SpringLifecycleStartHandler startHandler : startHandlers.values()) {
assertTrue(startHandler.isRunning());
}
for (SpringLifecycleShutdownHandler shutdownHandler : shutdownHandlers.values()) {
assertTrue(shutdownHandler.isRunning());
}
AtomicBoolean startHandlerInvoked = context.getBean("startHandlerInvoked", AtomicBoolean.class);
assertTrue(startHandlerInvoked.get());
AtomicBoolean shutdownHandlerInvoked = context.getBean("shutdownHandlerInvoked", AtomicBoolean.class);
assertFalse(shutdownHandlerInvoked.get());
// I hope that this would invoke the shutdown handler, but it does not
context.stop();
// Validate the shutdown handler is invoked...
await().atMost(Duration.ofSeconds(5))
.pollDelay(Duration.ofMillis(25))
.until(shutdownHandlerInvoked::get);
});
}
As may become apparent, my test context constructs a single start handler and shutdown handler, which we wrap in a SpringLifecycleStartHandler
and SpringLifecycleShutdownHandler
(both SmartLifecycle
instances). Both the start and shutdown handlers receive an AtomicBoolean
which is flipped when the handler is invoked. Through that process, I aim to validate that (1) the start handler is invoked on start-up and (2) the shutdown handler is invoked on shutdown.
I have a hunch that what I am looking to test is simply impossible with an ApplicationContextRunner
, but I figured I'd shoot a question regardless. Thanks for any guidance in this area!
The reason my original test approach didn't succeed, is because my test suite touched to many points in one go...
The ApplicationContext#stop
invocation did, as you would expect, shutdown the application. And, thus allowed me to validate if the shutdown lifecycle beans were registered.
@Test
void lifecycleRegistryIntegratesWithDedicatedSpringLifecycleBeans() {
testContext.run(context -> {
// Left out rest for brevity...
// Simply stop the context, that's it!
context.stop();
// Validate the shutdown handler is invoked...
await().atMost(Duration.ofSeconds(5))
.pollDelay(Duration.ofMillis(25))
.until(shutdownHandlerInvoked::get);
});
}
I had another issue in my test suite that caused the shutdown to not trigger successfully.
So, my anticipation that ApplicationContext#stop
would shutdown the Spring Application Context was, well, correct.
I've updated my original question with the appropriate information as well.