Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing application restart with persistence with JUnit and Spring

I have an application that uses persistent JMS queues. Imagine I have an application failure after reading a message and before ack'ing it. The persistent queue must provide that message again after the app restarts. How can I implement a junit integration test for this? I'm testing application restart after a (simulated) application crash mid-"transaction".

I've looked at @DirtiesContext as a way to reset all the Spring parts of the app: reading configs, recreating JMS connections. I could have one test case A) write a message, allow the message to be read and then "exit" (shut down the spring context?) without acking. Then another test case (after the context is reloaded B) read the message and assert that it was not lost after the simulated application restart. But the builtin context reload provided by @DirtiesContext only happens between test cases. And JUnit does not provide for a means to sequence two test cases or make B) dependent on A), such that A) will always run (and run first) if you decide to run B).

In a previous life, I wrote manual code that shut down the spring context, and manually restarted a new context. E.g. between A) and B). That could be done within a single test case. It wouldn't play nicely with @RunWith(SpringRunner.class), I'm guessing, and seems pretty old school. Is that really the only option, given all the wonderful Spring and JUnit support these days?

This seems like a pretty useful technique. It could be used to test re-arrival of messages after they've been rolled back (and are stuck on a dead letter queue); or that sequence numbers written to a DB are really persisting during a "crash". Any number of failure cases that wind up affecting the next application startup due to persisted (or not) data. How do we simulate spring restart in junit tests? Either within one test, or create a sequence of dependent tests with @DirtiesContext between them.

like image 460
Darrin West Avatar asked Dec 01 '25 06:12

Darrin West


1 Answers

The following article How to Restart a Spring Application Context within a JUnit test describes a solution to the question.


The solution consist to expend SpringJUnit4ClassRunner in order to inject a SpringRestarter singleton which takes care of restating the application context.

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();
                    }
                });
    }
}

RestartingSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner and initialise the singleton SpringRestarter.

public class RestartingSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner {

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

    @Override
    protected TestContextManager createTestContextManager(Class<?> clazz) {

        final TestContextManager testContextManager = super.createTestContextManager(clazz);

        SpringRestarter.getInstance().init(testContextManager);
        return testContextManager;
    }
}

RestartingSpringRunner extends RestartingSpringJUnit4ClassRunner in order to extend SpringRunner

public class RestartingSpringRunner extends RestartingSpringJUnit4ClassRunner {

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

Using Within a JUnit 4 Test

@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
    }
}

Full details of the explanation

like image 151
Greg Jeanmart Avatar answered Dec 03 '25 21:12

Greg Jeanmart



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!