Spring Boot 2.2.5 JUnit 5.6.2
In my codebase I have:
- Tests using
@SpringBootTest, leveraging it's context caching for test execution speed. - Tests using Reactor's
withVirtualTime
The tests using @SpringBootTest setup some components that use a Flux to communicate, and also using the Schedulers that Reactor provides.
withVirtualTime operates by replacing the Schedulers with ones that can have time manipulated. As part of this, it disposes the existing schedulers, does the replacement, and then restores the default schedulers.
However, this will cause subsequent tests from the first category to then fail with
reactor.core.Exceptions$ReactorRejectedExecutionException: Scheduler unavailable as the scheduler the Flux's were using has been disposed.
In terms of remediation, controlling the test execution order isn't available right now (and has it's own problems, as mentioned in https://github.com/junit-team/junit5/issues/1948).
I've "worked around" the issue by having tests in the second category "use" the same set of annotations as the first category, so that I can add @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) and force the context to be disposed. This is a tractable solution for me as I only have one type of cached context.
Other ways of solving this could be:
- A method to not have withVirtualTime replace the default Schedulers (with the knowledge that the code under test will always need to specify which scheduler to use for the operators that use a default)
- A method to forcibly close all cached contexts. I thought about sub-classing DefaultCacheAwareContextLoaderDelegate to get at the static defaultContextCache, but there is no way to close all contexts. The cache may be cleared, but that will cause the contexts to be orphaned in memory.
Comment From: wilkinsona
Thanks for the report.
As I understand the problem, I don't think there's anything about it that is specific to Spring Boot. It sounds like a general problem with any usage of the context cache provided Spring Framework's testing support and Reactor's withVirtualTime. As such, the ideal place to solve the problem would be in Framework. If that's not possible for some reason and a solution would rely on some of the knowledge about an app that only Boot has, we can look at a solution here but I think the Framework team should take the first look.
Comment From: smaldini
cc @simonbasle
I wonder if we should introduce an annotation in reactor-test or elsewhere (@VirtualTime) that would apply virtual time to an entire test class probably with JUnit in target. At least an annotation would give better lifecycle integration with Spring testing facilities.
Comment From: simonbasle
@osi you can certainly use VirtualTimeScheduler as a standalone Scheduler instead of relying on withVirtualTime to create the VTS for you and set it up / clean it. Have you explored that possibility?
Comment From: osi
@simonbasle I did, a little. I want to be able to use the virtual-time capabilities of the StepVerifier, and creating a VirtualTimeScheduler outside of it would mean that we'd loose those niceties (and require explaining to co-workers why we deviate from other examples).
However, that may work as long as we are diligent about not relying on using the default schedulers and ensure that any flow that we want to test with virtual time uses a provided scheduler (which would be the virtual time instance when under test).
That is certainly a workaround, albeit undesirable. It would be nice to be able to use the two features in question (context caching in tests and virtual time in a step verifier) together in tests without worrying about how they interact (at the obvious cost of not having the context be cached if a test using virtual time is run)
Comment From: rstoyanchev
I think it makes sense to explore a solution in Reactor first since that is where the virtualTime feature is. There is actually a third idea about how this can be handled, see https://github.com/reactor/reactor-core/issues/2325 which was created to explore that. In the mean time I'm closing this for now as there are no further plans in Spring. We can always re-open.
Comment From: simonbasle
I explored offering a method to temporarily set a Schedulers.Factory and capture the previous state + a method to restore said captured state. please have a look at reactor/reactor-core#2326. Note that in this PR, the VirtualTimeScheduler methods that set/reset the Schedulers.Factory do capture and restore the Schedulers.Snapshot.