Welcome

EmbeddedRedis Support For Spring Framework

When I was working on one of our microservice projects, I’ve come up with a nice project which help us to run a Redis Server as embedded within our Java integration tests. Actually, by using such a library, it becomes very easy to start and stop a Redis server instance within the test methods.

RedisServer redisServer = new RedisServer(6379);
redisServer.start();
// do some work
redisServer.stop();

However, there appear some other commonly recurring requirements, while we are coding our Java integration tests, that are not satisfied by the project. For example, you will probably want to run the Redis server on a random port rather than the well-known Redis server port, because an external server instance might be running within your test execution environment already. You might also not want to start and stop the server for each test method separately, but make it run once and keep it running for a bunch of test cases.

We are already using Spring Boot in our current project, and making heavy use of the Spring TestContext Framework in order to write and run our integration tests. Hence, I thought that it would be nice to have a mechanism to transparently run the embedded Redis server on a random port and keep it running along with the multiple test cases. Actually, Spring Kafka Test project already provides something similar to what was in my mind. When you place @EmbeddedKafka annotation on top of your test classes, Spring will run EmbeddedKafkaBroker behind the scenes, make it available as a bean during the execution of integration tests, and keep it running as long as the ApplicationContext, which is loaded during the execution of the tests, is open. If you are a veteran Spring user, you might already know that the Spring TestContext Framework has ApplicationContext caching support, so that it won’t create a separate ApplicationContext for two different test classes, unless they have different context configurations. Therefore, your embedded Kafka broker is kept open across the execution of several test cases, which is I expect from the embedded Redis server, too.

Anyway, at this point, I decided that it would be nice to implement a similar feature for the embedded Redis server in our Spring enabled project. Thankfully, Spring already provides a simple mechanism to identify an available port in the current environment so that the embedded Redis server could be bound to listen for the incoming requests. Spring TestContext Framework has also the necessary SPI mechanism to customize ApplicationContext creation during the execution of tests so that we can easily hook in and configure our embedded Redis server instance as a Spring-managed bean as well.

The Spring TestContext Framework SPI for ApplicationContext customization is called ContextCustomizer. It allows us to customize ApplicationContexts that are created and managed by the framework. Behind the scenes, it is based on Spring’s SpringFactoriesLoader mechanism. You need to provide your custom ContextCustomizer instance via a ContextCustomizerFactory which is loaded by the SpringFactoriesLoader. You need to define the FQN of your ContextCustomizerFactory class within a META-INF/spring.factories file so that it can be identified by the Spring.

I first started to work by defining @EmbeddedRedis annotation which will be used on top of test classes for which we will instantiate and run embedded Redis server.

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class EmbeddedRedis(val port:Int = 6379, val useRandomPort:Boolean=true)

Next is the EmbeddedRedisContextCustomizerFactory which is for providing custom ContextCustomizer if there is an @EmbeddedRedis annotation on top of the current test class.

class EmbeddedRedisContextCustomizerFactory : ContextCustomizerFactory {
    override fun createContextCustomizer(
        testClass: Class<*>,
        configAttributes: MutableList<ContextConfigurationAttributes>
    ): ContextCustomizer? {
        val embeddedRedis = AnnotatedElementUtils.findMergedAnnotation(testClass,EmbeddedRedis::class.java)
        if(embeddedRedis != null) return EmbeddedRedisContextCustomizer(embeddedRedis)
        else return null
    }
}

We also need to define this ContextCustomizerFactory in spring.factories file within the project classpath.

# Spring Test ContextCustomizerFactories
org.springframework.test.context.ContextCustomizerFactory=\
com.udemy.comms.EmbeddedRedisContextCustomizerFactory

The real job is done by the EmbeddedRedisContextCustomizer class. It checks @EmbeddedRedis annotation in order to identify if the port is specified by the developer, or a random port will be generated using the Spring’s utility method, then it instantiates EmbeddedRedisServer giving the port resolved at the previous step, and registers it as a Spring-managed singleton bean into the ApplicationContext.

class EmbeddedRedisContextCustomizer(private val embeddedRedis: EmbeddedRedis) : ContextCustomizer {
    override fun customizeContext(context: ConfigurableApplicationContext, mergedConfig: MergedContextConfiguration) {
        val beanFactory = context.getBeanFactory()
        var redisServerPort = embeddedRedis.port
        if(embeddedRedis.useRandomPort)
            redisServerPort = SocketUtils.findAvailableTcpPort()
        val redisServer = EmbeddedRedisServer(redisServerPort)

        val beanName = "embeddedRedisServer"
        beanFactory.initializeBean(redisServer,beanName)
        beanFactory.registerSingleton(beanName,redisServer)
        (beanFactory as DefaultSingletonBeanRegistry).registerDisposableBean(beanName,redisServer)
    }
}

EmbeddedRedisServer is actually a thin wrapper around the real embedded Redis server instance. It implements Spring’s lifecycle callback methods so that the actual server instance will be created and started during bean initialization, and stopped when the bean is destroyed which happens at ApplicationContext close time because the bean is registered as a singleton.

class EmbeddedRedisServer(private val port:Int) : InitializingBean, DisposableBean {

    private var redisServer: RedisServer = RedisServer(port)

    private val portProperty = "embedded.redis.port"

    override fun afterPropertiesSet() {
        if(!redisServer.isActive)
            redisServer.start()
        System.setProperty(portProperty, port.toString())
    }

    override fun destroy() {
        if(redisServer.isActive)
            redisServer.stop()
        System.getProperties().remove(portProperty)
    }

    fun getActualRedisServer() : RedisServer {
        return redisServer
    }
}

One of the most important tasks handled by the EmbeddedRedisServer wrapper is to expose the port number as a JVM system property so that it can be later on looked up by the other parts of the application, so that it can be used connect and access to the Redis server from within the application at runtime.

In summary, we have had a nice add-on on top of the embedded Redis server for our Spring enabled projects at the end of the day. It’s become super simple to create an embedded Redis server running on a random port and keep it running across our integration test cases in a Spring-supported environment.