craig

6 min read - Posted 20 Sep 19

How to Restart a Spring Application Context within a JUnit test

The Problem

When attempting to write some failover tests to verify the resilience features in Eventeum, there didn't seem to be an easy way to restart a Spring application in the middle of an integration test. Spring provides the @DirtiesContext annotation in order to rebuild the context before or after a test run, but this is not of much use for service failure testing unless you write a suite of tests that depend on each other, which is generally regarded as a bad practice.

The Solution

After doing a deep-dive into how the SpringRunner works behind the scenes, I managed to come up with a solution that enabled me to programmatically restart a service within a JUnit 4 test.

SpringRestarter singleton

A 'SpringRestarter' singleton class is defined which is the entry point used to restart the Spring application context within a test method.

public class SpringRestarter {

    private static SpringRestarter INSTANCE = null;

    private TestContextManager testContextManager;

    public static SpringRestarter getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new SpringRestarter();
        }

        return INSTANCE;
    }

    public void init(TestContextManager testContextManager) {
        this.testContextManager = testContextManager;
    }

    public void restart(Runnable stoppedLogic) {
        testContextManager.getTestContext().markApplicationContextDirty(DirtiesContext.HierarchyMode.EXHAUSTIVE);

        if (stoppedLogic != null) {
            stoppedLogic.run();
        }

        testContextManager.getTestContext().getApplicationContext();
        reinjectDependencies();
    }

    private void reinjectDependencies()  {
        testContextManager
                .getTestExecutionListeners()
                .stream()
                .filter(listener -> listener instanceof DependencyInjectionTestExecutionListener)
                .findFirst()
                .ifPresent(listener -> {
                    try {
                        listener.prepareTestInstance(testContextManager.getTestContext());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
    }
}
 init(..)

The SpringRestarter needs to be initialised with a TestContextManager object. The TestContext contains the application context restart code which is used by the @DirtiesContext annotation, which we will also be using in our implementation.

restart(..)

This is the method called by tests that wish to restart the application context. It delegates stopping the context to the testContext.markApplicationContextDirty(..) method, and subsequently restarts the context by calling getTestContext().getApplicationContext(). There is also an optional stoppedLogic method argument which can contain any logic that should be run after the context has been stopped, before it is restarted again.

The reinjectDependencies() method is then called...

reinjectDependencies()

As spring dependencies can be autowired into a test class, we need to reinject these dependencies after restarting. This method is slightly hacky, but rather then duplicating the code within the DependencyInjectionTestExecutionListener prepareTestInstance method, we iterate through all the configured test listeners until we find the correct listener and call prepareTestInstance(..) on that listener. This will then reinject any autowired Spring beans into your test.

Extending SpringJUnit4ClassRunner

We need to initialise the SpringRestarter singleton with a TestContextManager and this class is where this object is created, so it also makes sense to init the singleton here.

public class RestartingSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner {

    public RestartingSpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
    }

    @Override
    protected Object createTest() throws Exception {
        final Object testInstance = super.createTest();

        SpringRestarter.getInstance().init(getTestContextManager());

        return testInstance;
    }
}

We override the createTest(..) method (called to initialise the test class); after invoking the super class method to create the test instance, the SpringRestarter is initialised with the TestContextManager.

Extending SpringRunner

SpringRunner extends SpringJUnit4ClassRunner, and as we have now created a new extended version of this class, we also need to extend the SpringRunner class.

public class RestartingSpringRunner extends RestartingSpringJUnit4ClassRunner {

    public RestartingSpringRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
    }
}
Using Within a JUnit 4 Test

Once these building blocks are in place, its really quite simple to restart the Spring application context within your integration tests.

@RunWith(RestartingSpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyIntegrationTests {

    public void myRestartingTest() {
        //Some test logic before the context restart
        
        SpringRestarter.getInstance().restart(() -> {/* Some logic after context stopped */});
        
        //Some test logic after the context restart
    }
}

Simply run with the new RestartingSpringBootRunner, and force a context restart by calling restart on the SpringRestarter singleton...thats it!!

Summary

Having the ability to restart a Spring application context during an integration test is a very desirable feature, especially when testing the recovery behaviour of your application after it has rebooted.

This feature can be implemented by making a few trivial extensions to a couple of Spring test classes along with implementing a relatively simple restarter singleton. It is then very easy to integrate within your JUnit tests.

Created with Sketch.Content is"CC-BY-SA 4.0" licensed
Article On-chain
0 Comments
Related Articles
Running a Pantheon Node in Java Integration Tests

The first problem you are likely to meet when attempting to write integration tests for your Java Ethereum application is that you need a running node to connect to for sending transactions. One option to overcome this is to run a node yourself manually in the background, but this becomes hard to manage if you want to run your tests in a CI pipeline, and forcing all contributors to you codebase to run a node manually is not ideal. Luckily theres a better way! Prerequisites A running Docker daemo

Using Eventeum to Build a Java Smart Contract Data Cache

In this tutorial, I am going to walk you through how to build a service that caches data emitted via smart contract events, so that this data can be consumed by other services in your system. Why would I want to do this? It all boils down to usability of your dApp. Web3 applications with no middleware layer at all generally do not provide the kind of user experience that end users are familiar with in the traditional web2 world. Operations such as complex searches across data stored within smart