Partner – Microsoft – NPI (cat= Spring)
announcement - icon

Azure Spring Apps is a fully managed service from Microsoft (built in collaboration with VMware), focused on building and deploying Spring Boot applications on Azure Cloud without worrying about Kubernetes.

And, the Enterprise plan comes with some interesting features, such as commercial Spring runtime support, a 99.95% SLA and some deep discounts (up to 47%) when you are ready for production.

>> Learn more and deploy your first Spring Boot app to Azure.

You can also ask questions and leave feedback on the Azure Spring Apps GitHub page.

1. Introduction

Starting with JUnit 4, tests can be run in parallel to gain speed for larger suites. The problem was concurrent test execution was not fully supported by the Spring TestContext Framework prior to Spring 5.

In this quick article, we’ll show how to use Spring 5 to run our tests in Spring projects concurrently.

2. Maven Setup

As a reminder, to run JUnit tests in parallel, we need to configure the maven-surefire-plugin to enable the feature:

<build>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.22.2</version>
        <configuration>
            <parallel>methods</parallel>
            <useUnlimitedThreads>true</useUnlimitedThreads>
        </configuration>
    </plugin>
</build>

You can check out the reference documentation for a more detailed configuration on parallel test execution.

3. Concurrent Test

The following example test would fail when running in parallel for versions prior to Spring 5.

However, it will run smoothly in Spring 5:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = Spring5JUnit4ConcurrentTest.SimpleConfiguration.class)
public class Spring5JUnit4ConcurrentTest implements ApplicationContextAware, InitializingBean {

    @Configuration
    public static class SimpleConfiguration {}

    private ApplicationContext applicationContext;

    private boolean beanInitialized = false;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.beanInitialized = true;
    }

    @Override
    public void setApplicationContext(
      final ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Test
    public void whenTestStarted_thenContextSet() throws Exception {
        TimeUnit.SECONDS.sleep(2);
 
        assertNotNull(
          "The application context should have been set due to ApplicationContextAware semantics.",
          this.applicationContext);
    }

    @Test
    public void whenTestStarted_thenBeanInitialized() throws Exception {
        TimeUnit.SECONDS.sleep(2);
 
        assertTrue(
          "This test bean should have been initialized due to InitializingBean semantics.",
          this.beanInitialized);
    }
}

When running sequentially, the tests above would take around 6 seconds to pass. With concurrent execution, it will only take about 4.5 seconds – which is quite typical for how much time we can expect to save in larger suites as well.

4. Under the Hood

The primary reason prior versions of the framework didn’t support running tests concurrently was due to the management of TestContext by the TestContextManager.

In Spring 5, the TestContextManager uses a thread local – TestContext – to ensure that operations on TestContexts in each thread would not interfere with each other. Thus thread-safety is guaranteed for most method level and class level concurrent tests:

public class TestContextManager {

    // ...
    private final TestContext testContext;

    private final ThreadLocal<TestContext> testContextHolder = new ThreadLocal<TestContext>() {
        protected TestContext initialValue() {
            return copyTestContext(TestContextManager.this.testContext);
        }
    };

    public final TestContext getTestContext() {
        return this.testContextHolder.get();
    }

    // ...
}

Note that the concurrency support does not apply to all kinds of tests; we need to exclude tests that:

  • change external shared states, such as states in caches, databases, message queues, etc.
  • require specific execution orders, for example, tests that use JUnit‘s @FixMethodOrder
  • modify the ApplicationContext, which are generally marked by @DirtiesContext

5. Summary

In this quick tutorial, we’ve shown a basic example using Spring 5 to run tests in parallel.

As always, the example code can be found over on Github.

Course – LS (cat=Spring)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.