2

Description

We have a growing application with numerous integration tests which involve connection to a redis DB. Because of the growing numbers we want to parallelize them at least at class level. Till now we did run all tests sequentially and started (stopped) an embedded redis DB (com.github.kstyrc embedded-redis 0.6) in the static @BefroreClass/@AfterClass methods (jUnit 4). The port of the DB is always the same -- 9736. This is also set in the application.properties via spring.redis.port=9736 for our jedis connection pool. For the parallelization to work we have to get our port dynamically as well as announce it to the connection factory for connection pooling. This problem I got solved after some time by implementing BeanPostProcessor in a configuration. The remaining issue I have is with the correct interception of the bean lifecycle and the web application context.

Code snippets parallel testing

application.properties

...
spring.redis.port=${random.int[4000,5000]}
...

The BeanPostProcessor implementing config

@Configuration
public class TestConfig implements BeanPostProcessor {

    private RedisServer redisServer;

    private int redisPort;

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (JedisConnectionFactory.class.equals(bean.getClass())) {
            redisPort = ((JedisConnectionFactory) bean).getPort();
            redisServer().start();
        }
        return bean;
    }

    @Bean(destroyMethod = "stop")
    public RedisServer redisServer() {
        redisServer = RedisServer.builder().port(redisPort).build();
        return redisServer;
    }
}

Startup and shutdown for parallel testing with dynamic port

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class OfferControllerTest {

    private MockMvc mockMvc;

    @Inject
    protected WebApplicationContext wac;
...
    @Before
    public void setup() throws Exception {
        this.mockMvc = webAppContextSetup(this.wac).apply(springSecurity()).build();
    }

    @After
    public void tearDown() throws Exception {
        offerRepository.deleteAll();
    }
...

Test parallelization is achieved trough maven-surefire-plugin 2.18.1

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.18.1</version>
    <configuration>
        <parallel>classes</parallel>
        <threadCount>4</threadCount>
    </configuration>
</plugin>

Supplement

What happens is, that during springs bean inititialization phase our TestConfig hooks into the lifecycle of the JedisConnectionFactory bean and starts a redis server on the random choosen port through spring.redis.port=${random.int[4000,5000]} before the connection pool is initiated. Since the redisServer itself is a bean we use the destroyMethod to stop the server on bean destruction and therefore leaving this to the application context lifecycle. The transition from sequential to parallel went well regarding static port to dynamic port.

Problem

But when I run the tests in parallel I get errors like these:
java.lang.IllegalStateException: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@22b19d79 has been closed already through

@Before
public void setup() throws Exception {
    this.mockMvc = webAppContextSetup(this.wac).apply(springSecurity()).build();
}

and
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties': Initialization of bean failed; nested exception is java.lang.IllegalStateException: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@22b19d79 has been closed already through

@After
public void tearDown() throws Exception {
    offerRepository.deleteAll();
}

Help

I am not really sure about the problem. Maybe we can ommit the tearDown call to offerRepository.deleteAll()
because of @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
but the error at setup webAppContextSetup(this.wac).apply(springSecurity()).build() would still remain.

Did the application contexts get screwed when running in parallel or why is the application context in setup already been closed?
Did we choose the wrong approach (wrong pattern)? If so, what should we change?

MonkeyTonk
  • 55
  • 10

0 Answers0