When running multiple tests the application contexts are not closed after each test class, which is the defined behavior.
If multiple tests are starting a Quartz Scheduler, this may lead to some cached scheduler starting jobs in the wrong application context. The obvious solution is to mark all those tests with @DirtiesContext
.
I wonder if this can be done automatically by the spring test framework.
So you can't forget the annotation on some test.
It would also save some other folks time (like this guys: https://stackoverflow.com/questions/54818080/quartz-job-from-a-previous-spring-boot-test-randomly-still-running/59234469#59234469) - it took me 5h debugging this until it fell like scales from my eyes.
Comment From: joshiste
My current approach:
public class QuartzTestExecutionListener extends AbstractDirtiesContextTestExecutionListener {
private static final Logger log = LoggerFactory.getLogger(QuartzTestExecutionListener.class);
@Override
public void afterTestClass(TestContext testContext) throws Exception {
var schedulers = testContext.getApplicationContext().getBeansOfType(Scheduler.class);
if (!schedulers.isEmpty()) {
log.info("Quartz Scheduler present - dirtying ApplicationContext to force shutdown");
this.dirtyContext(testContext, DirtiesContext.HierarchyMode.EXHAUSTIVE);
}
}
@Override
public int getOrder() {
return 0;
}
}
Comment From: dkubicki
Thanks, save us a few hours :)
Comment From: Antibrumm
I ran into exactly this problem too and think I found a better solution that does not require dirtying the context:
- The general idea is to put the scheduler into standby mode after a test class and reactivate it before the test class.
@SpringBootQuartzAwareTest
public class C1Tests {
@Test
void someTestContext1() {
}
}
@SpringBootQuartzAwareTest
@TestPropertySource(properties = { "someproperty=should_start_a_new_context" })
public class C2Tests {
@Test
void someTestContext2() {
}
}
@SpringBootTest
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TestExecutionListeners(
listeners = QuartzAwareTestExecutionListener.class,
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
)
public @interface SpringBootQuartzAwareTest {
}
@Slf4j
public class QuartzAwareTestExecutionListener implements TestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) {
var schedulers = testContext.getApplicationContext().getBeansOfType(Scheduler.class);
if (!schedulers.isEmpty()) {
schedulers.forEach((name, scheduler) -> {
try {
if (scheduler.isInStandbyMode()) {
log.info("Quartz Scheduler in standby present - calling start for {}", name);
scheduler.start();
}
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
});
}
}
@Override
public void afterTestClass(TestContext testContext) {
var schedulers = testContext.getApplicationContext().getBeansOfType(Scheduler.class);
if (!schedulers.isEmpty()) {
schedulers.forEach((name, scheduler) -> {
try {
if (!scheduler.isInStandbyMode() && !scheduler.isShutdown()) {
log.info("Quartz Scheduler present - calling standby for {}", name);
scheduler.standby();
}
} catch (SchedulerException e) {
throw new RuntimeException(e);
}
});
}
}
}
Comment From: snicoll
I guess there are many other use cases where you wouldn't want a bean to stay around in a context that has been started by the TCF. I don't really see the scheduler to be particular in that sense. Caching with a backend store may be affected as well.
For those use cases, we generally recommend to separate the impacted configuration so that you can better tune the content of the context. Or replace the production instance (Quartz) by something more simple. Unfortunately, that's how the TCF works and figuring out if a particular bean requires a particular lifecycle is out of our control. We could introduce support for this but I don't know how we could since it feels quite test specific.
Thoughts?
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: spring-projects-issues
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.