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?