In the rapidly advancing domain of software testing, the optimization of testing processes is important. The challenge lies in maintaining an efficient testing environment while minimizing the time spent on test initialization. This article explores a fresh approach to this problem: caching a Spring context between test runs.
Let’s dive into an IntelliJ IDEA plugin designed to speed up the process of local testing.
The problem
How much time do you typically spend running tests locally? If you are using the Spring Framework and have a big enough project, it’s likely that test initialization consumes a significant amount of time, as is the case with Wrike. Basically, you have to wait for the initialization process every time you run your tests.
Did you add a new test? Have you made a minor fix and want to verify if it’s working? Or perhaps you missed a function to step into during debugging? You’ll have to wait anywhere from a few seconds to a few minutes (depending on your case) to continue your work!
To mitigate this issue, at least partially, I developed an IntelliJ IDEA plugin.
The plugin’s capabilities
The idea is as follows:
Firstly, we aim to avoid shutting down the JVM after each test run and keep the Spring context alive between test runs. This way, we only spend time on the test initialization the first time we run tests in our JVM.
Secondly, we also need to understand how we will update our code while the JVM is up and running. To achieve this, let’s use the JVM Hotswap feature to enable code editing on the fly. To further improve the developer experience, we can use DCEVM and Hotswap Agent. More details follow in the next sections.
Lastly, we encapsulate all of this within the IntelliJ IDEA plugin, making it user friendly. This allows us to run tests reusing the same JVM in a familiar environment.
How to use the plugin
Using the plugin is straightforward: Once you locate the test you wish to run, a new option will appear in the dropdown menu with run options for this test — “Run in Launched JVM.”
When you click this, the project will be built, a process will start with a debugger attached, and your chosen test will be executed.
And here comes the difference: The process is still up and waiting for the next command to execute the test. The next time you press the same button (Run in Launched JVM), it will perform the hotswap of changed files and run the tests you have chosen.
What to be aware of
This approach works because it preserves the application state between test runs. Under the hood, Spring keeps its context in a static variable, making it straightforward to reuse this context.
But it is also a curse. For instance, if you modify the context configuration, it will not automatically update or refresh. Or if you have some kind of a cache, the state of the cache will be preserved between test runs unless you manually reset it.
Another limitation lies in our use of the JVM Hotswap feature for code updates. By default, JVM Hotswap is quite limited — it can only hotswap method bodies, which is insufficient for comfortable development.
Fortunately, we have DCEVM and Hotswap Agent! Basically, DCEVM is the HotSpot patch that enables many more cases for hotswapping. Starting from Java SE 17, JetBrains Runtime (JBR) includes the DCEVM implementation so you can use it.
Hotswap Agent is a Java agent that enables you to execute code when a resource or class is loaded for the first time or reloaded. This means it’s now possible to update the Spring context or other part of the application state when you perform a hotswap.
Hotswap Agent has a plugin system and quite a few plugins already implemented for you. For example, there are plugins for Spring, Hibernate, etc. From my experience, they may not work on all library versions, but can still be very useful. Plus, you can implement such plugins yourself to make custom hotswap logic specific to your project.
How to use DCEVM with the plugin
First of all, you need to install DCEVM itself and select it as a project SDK. Then, you should go to the plugin settings in IntelliJ IDEA (Settings → Tools → Sprinter Settings) and select DCEVM in the “Configured JVM type” input.
You’ll notice several settings related to DCEVM. Now you can start using this plugin with the configured DCEVM.
To use Hotswap Agent within the Sprinter plugin, you need to JetBrains Runtime (JBR) and specify its location.
Hotswap Agent developers recommend placing custom plugins alongside the production code. However, creating plugins for Hotswap Agent in separate project modules may offer better separation between development-specific code and all other code.
Using “Modules with custom hotswap agent plugins,” you can specify a list of project modules where your custom plugins are located. The Sprinter plugin will compile these — along with the rest of the project — and add them to the classpath, ensuring they are used. Don’t forget to specify the package where your plugins are located so that Hotswap Agent can discover them.
The plugin will create a hotswap-agent.properties file and populate it with content from the “Hotswap properties” input. You can find documentation regarding this file here.
Also, while not the primary focus of the plugin, you can use the configured DCEVM to run a regular application, not just tests. To do this, you should persist the configuration you want to run in IntelliJ IDEA and add it in the settings in the “Configurations for which DCEVM will be configured” input.
The next time you will run it, all DCEVM settings will be applied for this configuration.
To perform the hotswap, you can use the regular IntelliJ IDEA functionality (Run → Debugging Actions → Reload Changed Classes). Please note that there is an issue: It will try to compile the whole project instead of only the modules used by your application. To address this, I’ve created a separate action (Build → Compile Modules For Running Configurations), which does the same as the described action but compiles only the required modules, thereby speeding up hotswapping in larger projects.
Conclusion
In summary, the IntelliJ IDEA Sprinter plugin presents a promising solution for speeding up the local testing process. By leveraging the JVM Hotswap feature, we can enhance the efficiency of test initialization.
However, it’s essential to be aware of the limitations and potential issues, such as maintaining the application state and managing the context configuration. The plugin’s potential extends beyond the Spring context, potentially saving time with Testcontainers and other instruments that require significant initialization time. As software testing continues to evolve, tools like this plugin will be instrumental in managing and optimizing the testing process.
For more information about this plugin, check out this recording of me talking about it at a meetup or the repository with the source code.
This article was written by a Wriker, in Wrike. See what it’s like to work with us and what career development opportunities we offer here.
Also, hear from our founder, Andrew Filev, about Wrike’s culture and values, the ways we work and appreciate Wrikers, and more here.