I'm doing integration tests in my Spring Boot app. The app needs a Redis to work with.
During development phase, I have a local container of Redis that the app connects to.
For the integration tests, I'm using testcontainers and I also followed their example of how to use a Redis container.
At some point I understood that the test run correctly only when the development container was up and running. If it is down, the integration tests are falling because they can't reach Redis.
So the integration test class looks like that:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SharkApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-integrationtests.yml")
@AutoConfigureMockMvc
public class SharkIntegrationTest {
static GenericContainer redis = new GenericContainer("redis:3.0.6")
.withExposedPorts(6379);
@BeforeClass
public static void before(){
redis.start();
}
@AfterClass
public static void after(){
redis.stop();
}
...
When running the test, I can see this in the log:
14:36:24.372 [main] DEBUG 🐳 [redis:3.0.6] - Starting container: redis:3.0.6
14:36:24.372 [main] DEBUG 🐳 [redis:3.0.6] - Trying to start container:
redis:3.0.6
14:36:24.373 [main] DEBUG 🐳 [redis:3.0.6] - Trying to start container:
redis:3.0.6 (attempt 1/1)
14:36:24.373 [main] DEBUG 🐳 [redis:3.0.6] - Starting container: redis:3.0.6
14:36:24.373 [main] INFO 🐳 [redis:3.0.6] - Creating container for image:
redis:3.0.6
...
14:36:25.282 [main] INFO 🐳 [redis:3.0.6] - Container redis:3.0.6 started
But then the app fails as it can't reach Redis:
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused: connect
At some point I tried to change the port on which the container should start. From 6379 to 16379 (changed both in the code and in the yml file), but then the test is entering an endless loop and prints to the screen:
14:41:57.258 [ducttape-0] DEBUG org.testcontainers.containers.ExecInContainerPattern - /amazing_beaver: Running "exec" command: /bin/bash -c </dev/tcp/localhost/16379 && echo
You're missing a very important aspect of Testcontainers - random ports.
From the link you mentioned:
For example, with the Redis example above, the following will allow your tests to access the Redis service:
String redisUrl = redis.getContainerIpAddress() + ":" + redis.getMappedPort(6379);
Testcontainers starts everything with random ports to avoid conflicts.
You can follow this workshop to integrate it properly.
When you declare a container this way:
static GenericContainer redis = new GenericContainer("redis:3.0.6")
.withExposedPorts(6379);
You are telling TestContainers to map a random host port to the container port 6379
. As shown in the following screenshot, for example, TestContainers mapped from host port 32881
to container port 6379
:
To access the Redis container in a test, you need to use the random host port, not the redis port 6379
. To do so, you need to override (in runtime) the configuration values defined in application.properties
to use the random host port.
Here is how you can do it:
package some.random.packagee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.testcontainers.containers.GenericContainer;
@SpringBootTest
@ContextConfiguration(initializers = some.random.packagee.AbstractContainerBaseTest.Initializer.class)
public class AbstractContainerBaseTest {
private static final int REDIS_PORT = 6379;
// Optional
@Autowired
private RedisTemplate redisTemplate;
// Optional
protected void cleanCache() {
redisTemplate.getConnectionFactory().getConnection().flushAll();
}
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
static GenericContainer redis = new GenericContainer<>("redis:6-alpine")
.withExposedPorts(REDIS_PORT)
.withReuse(true);
@Override
public void initialize(ConfigurableApplicationContext context) {
// Start container
redis.start();
// Override Redis configuration
String redisContainerIP = "spring.redis.host=" + redis.getContainerIpAddress();
String redisContainerPort = "spring.redis.port=" + redis.getMappedPort(REDIS_PORT); // <- This is how you get the random port.
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, redisContainerIP, redisContainerPort); // <- This is how you override the configuration in runtime.
}
}
}
Then you extend the class AbstractContainerBaseTest
in the classes that require to use Redis, for example:
package some.random.packagee;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
class CacheTest extends AbstractContainerBaseTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@AfterEach
void tearDown() {
cleanCache();
}
@Test
public void testSomeMethodUsingRedis() {
// Add your test here.
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With