Skip to content

Commit 5b3519a

Browse files
committed
spring-projectsGH-3840: Migrate Redis tests to Testcontainers
Fixes spring-projects#3840 * Create a new abstraction `RedisTest` for Testcontainers-based tests * Move existing common utility methods to the new interface * Migrate all existing Redis tests to use this new `RedisTest` interface * Migrate all existing Redis tests to JUnit5 * Make all existing Redis tests confirming to JUnit5 standards, such as default methods and classes visibility instead of public * Add a dependency for parametrized tests in JUnit5 * Improve assertions readability across tests, for instance: assertThat(thing.size()).isEqualTo(2) -> assertThat(thing).hasSize(2) * Add real assertions to `RedisLockRegistryTests.twoRedisLockRegistryTest` (earlier it did not have assertions at all) * Reformat, rearrange and cleanup the code
1 parent 79d31b2 commit 5b3519a

File tree

44 files changed

+1137
-1358
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1137
-1358
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ configure(javaProjects) { subproject ->
260260
exclude group: 'org.hamcrest'
261261
}
262262
testImplementation 'org.junit.jupiter:junit-jupiter-api'
263+
testImplementation 'org.junit.jupiter:junit-jupiter-params'
263264
testImplementation("com.willowtreeapps.assertk:assertk-jvm:$assertkVersion") {
264265
exclude group: 'org.jetbrains.kotlin'
265266
}

spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2019 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,17 +28,18 @@
2828
import java.util.concurrent.atomic.AtomicBoolean;
2929

3030
import org.apache.geode.cache.CacheFactory;
31-
import org.junit.Test;
31+
import org.junit.jupiter.api.BeforeAll;
32+
import org.junit.jupiter.api.Test;
3233
import org.mockito.Mockito;
3334

35+
import org.springframework.data.redis.connection.RedisConnectionFactory;
3436
import org.springframework.data.redis.core.RedisTemplate;
3537
import org.springframework.data.redis.serializer.StringRedisSerializer;
3638
import org.springframework.integration.gemfire.metadata.GemfireMetadataStore;
3739
import org.springframework.integration.jdbc.metadata.JdbcMetadataStore;
3840
import org.springframework.integration.metadata.ConcurrentMetadataStore;
41+
import org.springframework.integration.redis.RedisTest;
3942
import org.springframework.integration.redis.metadata.RedisMetadataStore;
40-
import org.springframework.integration.redis.rules.RedisAvailable;
41-
import org.springframework.integration.redis.rules.RedisAvailableTests;
4243
import org.springframework.jdbc.core.JdbcTemplate;
4344
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
4445
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
@@ -48,23 +49,30 @@
4849
* @author Gary Russell
4950
* @author Artem Bilan
5051
* @author Bojan Vukasovic
52+
* @author Artem Vozhdayenko
5153
*
5254
* @since 4.0
5355
*
5456
*/
55-
public class PersistentAcceptOnceFileListFilterExternalStoreTests extends RedisAvailableTests {
57+
public class PersistentAcceptOnceFileListFilterExternalStoreTests implements RedisTest {
58+
59+
static RedisConnectionFactory redisConnectionFactory;
60+
61+
@BeforeAll
62+
static void setupConnectionFactory() {
63+
redisConnectionFactory = RedisTest.connectionFactory();
64+
}
5665

5766
@Test
58-
@RedisAvailable
5967
public void testFileSystemWithRedisMetadataStore() throws Exception {
6068
RedisTemplate<String, ?> template = new RedisTemplate<>();
61-
template.setConnectionFactory(this.getConnectionFactoryForTest());
69+
template.setConnectionFactory(redisConnectionFactory);
6270
template.setKeySerializer(new StringRedisSerializer());
6371
template.afterPropertiesSet();
6472
template.delete("persistentAcceptOnceFileListFilterRedisTests");
6573

6674
try {
67-
this.testFileSystem(new RedisMetadataStore(this.getConnectionFactoryForTest(),
75+
this.testFileSystem(new RedisMetadataStore(redisConnectionFactory,
6876
"persistentAcceptOnceFileListFilterRedisTests"));
6977
}
7078
finally {
@@ -95,8 +103,8 @@ public void testFileSystemWithJdbcMetadataStore() throws Exception {
95103
List<Map<String, Object>> metaData = new JdbcTemplate(dataSource)
96104
.queryForList("SELECT * FROM INT_METADATA_STORE");
97105

98-
assertThat(metaData.size()).isEqualTo(1);
99-
assertThat(metaData.get(0).get("METADATA_VALUE")).isEqualTo("43");
106+
assertThat(metaData).hasSize(1);
107+
assertThat(metaData.get(0)).containsEntry("METADATA_VALUE", "43");
100108
}
101109
finally {
102110
dataSource.shutdown();
@@ -127,28 +135,28 @@ private void testFileSystem(ConcurrentMetadataStore store) throws Exception {
127135
final FileSystemPersistentAcceptOnceFileListFilter filter =
128136
new FileSystemPersistentAcceptOnceFileListFilter(store, "foo:");
129137
final File file = File.createTempFile("foo", ".txt");
130-
assertThat(filter.filterFiles(new File[] { file }).size()).isEqualTo(1);
138+
assertThat(filter.filterFiles(new File[] {file})).hasSize(1);
131139
String ts = store.get("foo:" + file.getAbsolutePath());
132140
assertThat(ts).isEqualTo(String.valueOf(file.lastModified()));
133-
assertThat(filter.filterFiles(new File[] { file }).size()).isEqualTo(0);
134-
file.setLastModified(file.lastModified() + 5000L);
135-
assertThat(filter.filterFiles(new File[] { file }).size()).isEqualTo(1);
141+
assertThat(filter.filterFiles(new File[] {file})).isEmpty();
142+
assertThat(file.setLastModified(file.lastModified() + 5000L)).isTrue();
143+
assertThat(filter.filterFiles(new File[] {file})).hasSize(1);
136144
ts = store.get("foo:" + file.getAbsolutePath());
137145
assertThat(ts).isEqualTo(String.valueOf(file.lastModified()));
138-
assertThat(filter.filterFiles(new File[] { file }).size()).isEqualTo(0);
146+
assertThat(filter.filterFiles(new File[] {file})).isEmpty();
139147

140148
suspend.set(true);
141-
file.setLastModified(file.lastModified() + 5000L);
149+
assertThat(file.setLastModified(file.lastModified() + 5000L)).isTrue();
142150

143151
Future<Integer> result = Executors.newSingleThreadExecutor()
144-
.submit(() -> filter.filterFiles(new File[] { file }).size());
152+
.submit(() -> filter.filterFiles(new File[] {file}).size());
145153
assertThat(latch2.await(10, TimeUnit.SECONDS)).isTrue();
146154
store.put("foo:" + file.getAbsolutePath(), "43");
147155
latch1.countDown();
148156
Integer theResult = result.get(10, TimeUnit.SECONDS);
149157
assertThat(theResult).isEqualTo(Integer.valueOf(0)); // lost the race, key changed
150158

151-
file.delete();
159+
assertThat(file.delete()).isTrue();
152160
filter.close();
153161
}
154162

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,21 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.integration.redis.rules;
17+
package org.springframework.integration.redis;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
2020

21-
import org.junit.AfterClass;
22-
import org.junit.BeforeClass;
23-
import org.junit.Rule;
21+
import java.time.Duration;
22+
23+
import org.junit.jupiter.api.BeforeAll;
24+
import org.testcontainers.containers.GenericContainer;
25+
import org.testcontainers.junit.jupiter.Testcontainers;
2426

2527
import org.springframework.data.redis.connection.RedisConnection;
2628
import org.springframework.data.redis.connection.RedisConnectionFactory;
29+
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
30+
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
31+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
2732
import org.springframework.data.redis.core.BoundListOperations;
2833
import org.springframework.data.redis.core.BoundZSetOperations;
2934
import org.springframework.data.redis.core.RedisTemplate;
@@ -33,36 +38,64 @@
3338
import org.springframework.integration.test.util.TestUtils;
3439
import org.springframework.messaging.Message;
3540

41+
import io.lettuce.core.ClientOptions;
42+
import io.lettuce.core.SocketOptions;
43+
3644
/**
45+
* The base contract for all tests requiring a Redis connection.
46+
* The Testcontainers 'reuse' option must be disabled,so, Ryuk container is started
47+
* and will clean all the containers up from this test suite after JVM exit.
48+
* Since the Redis container instance is shared via static property, it is going to be
49+
* started only once per JVM, therefore the target Docker container is reused automatically.
50+
*
51+
* @author Artem Vozhdayenko
3752
* @author Oleg Zhurakousky
3853
* @author Gary Russell
3954
* @author Artem Bilan
4055
*
56+
* @since 6.0
4157
*/
42-
public abstract class RedisAvailableTests {
58+
@Testcontainers(disabledWithoutDocker = true)
59+
public interface RedisTest {
4360

44-
@Rule
45-
public RedisAvailableRule redisAvailableRule = new RedisAvailableRule();
61+
GenericContainer<?> REDIS_CONTAINER = new GenericContainer<>("redis:7.0.2")
62+
.withExposedPorts(6379);
4663

47-
@BeforeClass
48-
public static void setupConnectionFactory() {
49-
RedisAvailableRule.setupConnectionFactory();
64+
@BeforeAll
65+
static void startContainer() {
66+
REDIS_CONTAINER.start();
5067
}
5168

52-
@AfterClass
53-
public static void cleanUpConnectionFactoryIfAny() {
54-
RedisAvailableRule.cleanUpConnectionFactoryIfAny();
69+
/**
70+
* A primary method which should be used to connect to the test Redis instance.
71+
* Can be used in any JUnit lifecycle methods if a test class implements this interface.
72+
*/
73+
static LettuceConnectionFactory connectionFactory() {
74+
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
75+
redisStandaloneConfiguration.setPort(REDIS_CONTAINER.getFirstMappedPort());
76+
77+
LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
78+
.clientOptions(
79+
ClientOptions.builder()
80+
.socketOptions(
81+
SocketOptions.builder()
82+
.connectTimeout(Duration.ofMillis(10000))
83+
.keepAlive(true)
84+
.build())
85+
.build())
86+
.commandTimeout(Duration.ofSeconds(10000))
87+
.build();
88+
89+
var connectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfiguration);
90+
connectionFactory.afterPropertiesSet();
91+
return connectionFactory;
5592
}
5693

57-
protected RedisConnectionFactory getConnectionFactoryForTest() {
58-
return RedisAvailableRule.connectionFactory;
59-
}
60-
61-
protected void awaitContainerSubscribed(RedisMessageListenerContainer container) throws Exception {
94+
static void awaitContainerSubscribed(RedisMessageListenerContainer container) throws Exception {
6295
awaitContainerSubscribedNoWait(container);
6396
}
6497

65-
private void awaitContainerSubscribedNoWait(RedisMessageListenerContainer container) throws InterruptedException {
98+
static void awaitContainerSubscribedNoWait(RedisMessageListenerContainer container) throws InterruptedException {
6699
RedisConnection connection = null;
67100

68101
int n = 0;
@@ -82,8 +115,8 @@ private void awaitContainerSubscribedNoWait(RedisMessageListenerContainer contai
82115
assertThat(n < 300).as("RedisMessageListenerContainer Failed to Subscribe").isTrue();
83116
}
84117

85-
protected void awaitContainerSubscribedWithPatterns(RedisMessageListenerContainer container) throws Exception {
86-
this.awaitContainerSubscribed(container);
118+
static void awaitContainerSubscribedWithPatterns(RedisMessageListenerContainer container) throws Exception {
119+
awaitContainerSubscribed(container);
87120
RedisConnection connection = TestUtils.getPropertyValue(container, "subscriber.connection",
88121
RedisConnection.class);
89122

@@ -96,7 +129,7 @@ protected void awaitContainerSubscribedWithPatterns(RedisMessageListenerContaine
96129
Thread.sleep(1000);
97130
}
98131

99-
protected void awaitFullySubscribed(RedisMessageListenerContainer container, RedisTemplate<?, ?> redisTemplate,
132+
static void awaitFullySubscribed(RedisMessageListenerContainer container, RedisTemplate<?, ?> redisTemplate,
100133
String redisChannelName, QueueChannel channel, Object message) throws Exception {
101134
awaitContainerSubscribedNoWait(container);
102135
drain(channel);
@@ -110,13 +143,13 @@ protected void awaitFullySubscribed(RedisMessageListenerContainer container, Red
110143
assertThat(received).as("Container failed to fully start").isNotNull();
111144
}
112145

113-
private void drain(QueueChannel channel) {
146+
static void drain(QueueChannel channel) {
114147
while (channel.receive(0) != null) {
115148
// drain
116149
}
117150
}
118151

119-
protected void prepareList(RedisConnectionFactory connectionFactory) {
152+
static void prepareList(RedisConnectionFactory connectionFactory) {
120153

121154
StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory);
122155
redisTemplate.delete("presidents");
@@ -139,7 +172,7 @@ protected void prepareList(RedisConnectionFactory connectionFactory) {
139172
ops.rightPush("George Washington");
140173
}
141174

142-
protected void prepareZset(RedisConnectionFactory connectionFactory) {
175+
static void prepareZset(RedisConnectionFactory connectionFactory) {
143176

144177
StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory);
145178

@@ -163,20 +196,19 @@ protected void prepareZset(RedisConnectionFactory connectionFactory) {
163196
ops.add("George Washington", 18);
164197
}
165198

166-
protected void deletePresidents(RedisConnectionFactory connectionFactory) {
167-
this.deleteKey(connectionFactory, "presidents");
199+
static void deletePresidents(RedisConnectionFactory connectionFactory) {
200+
deleteKey(connectionFactory, "presidents");
168201
}
169202

170-
protected void deleteKey(RedisConnectionFactory connectionFactory, String key) {
203+
static void deleteKey(RedisConnectionFactory connectionFactory, String key) {
171204
StringRedisTemplate redisTemplate = createStringRedisTemplate(connectionFactory);
172205
redisTemplate.delete(key);
173206
}
174207

175-
protected StringRedisTemplate createStringRedisTemplate(RedisConnectionFactory connectionFactory) {
208+
static StringRedisTemplate createStringRedisTemplate(RedisConnectionFactory connectionFactory) {
176209
StringRedisTemplate redisTemplate = new StringRedisTemplate();
177210
redisTemplate.setConnectionFactory(connectionFactory);
178211
redisTemplate.afterPropertiesSet();
179212
return redisTemplate;
180213
}
181-
182214
}

spring-integration-redis/src/test/java/org/springframework/integration/redis/channel/SubscribableRedisChannelTests.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,14 +26,14 @@
2626
import java.util.concurrent.CountDownLatch;
2727
import java.util.concurrent.TimeUnit;
2828

29-
import org.junit.Test;
29+
import org.junit.jupiter.api.BeforeAll;
30+
import org.junit.jupiter.api.Test;
3031

3132
import org.springframework.beans.factory.BeanFactory;
3233
import org.springframework.data.redis.connection.RedisConnectionFactory;
3334
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
3435
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
35-
import org.springframework.integration.redis.rules.RedisAvailable;
36-
import org.springframework.integration.redis.rules.RedisAvailableTests;
36+
import org.springframework.integration.redis.RedisTest;
3737
import org.springframework.integration.test.util.TestUtils;
3838
import org.springframework.messaging.MessageHandler;
3939
import org.springframework.messaging.support.GenericMessage;
@@ -43,22 +43,26 @@
4343
* @author Oleg Zhurakousky
4444
* @author Gary Russell
4545
* @author Artem Bilan
46+
* @author Artem Vozhdayenko
4647
* @since 2.0
4748
*/
48-
public class SubscribableRedisChannelTests extends RedisAvailableTests {
49+
class SubscribableRedisChannelTests implements RedisTest {
50+
private static RedisConnectionFactory redisConnectionFactory;
4951

52+
@BeforeAll
53+
static void setupConnection() {
54+
redisConnectionFactory = RedisTest.connectionFactory();
55+
}
5056

5157
@Test
52-
@RedisAvailable
53-
public void pubSubChannelTest() throws Exception {
54-
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
58+
void pubSubChannelTest() throws Exception {
5559

56-
SubscribableRedisChannel channel = new SubscribableRedisChannel(connectionFactory, "si.test.channel");
60+
SubscribableRedisChannel channel = new SubscribableRedisChannel(redisConnectionFactory, "si.test.channel");
5761
channel.setBeanFactory(mock(BeanFactory.class));
5862
channel.afterPropertiesSet();
5963
channel.start();
6064

61-
this.awaitContainerSubscribed(TestUtils.getPropertyValue(channel, "container",
65+
RedisTest.awaitContainerSubscribed(TestUtils.getPropertyValue(channel, "container",
6266
RedisMessageListenerContainer.class));
6367

6468
final CountDownLatch latch = new CountDownLatch(3);
@@ -72,11 +76,9 @@ public void pubSubChannelTest() throws Exception {
7276
}
7377

7478
@Test
75-
@RedisAvailable
76-
public void dispatcherHasNoSubscribersTest() throws Exception {
77-
RedisConnectionFactory connectionFactory = this.getConnectionFactoryForTest();
79+
void dispatcherHasNoSubscribersTest() throws Exception {
7880

79-
SubscribableRedisChannel channel = new SubscribableRedisChannel(connectionFactory, "si.test.channel.no.subs");
81+
SubscribableRedisChannel channel = new SubscribableRedisChannel(redisConnectionFactory, "si.test.channel.no.subs");
8082
channel.setBeanName("dhnsChannel");
8183
channel.setBeanFactory(mock(BeanFactory.class));
8284
channel.afterPropertiesSet();

spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisChannelParserTests-context.xml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
<beans xmlns="http://www.springframework.org/schema/beans"
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xmlns:int-redis="http://www.springframework.org/schema/integration/redis"
5-
xmlns:util="http://www.springframework.org/schema/util"
65
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
7-
http://www.springframework.org/schema/integration/redis https://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd
8-
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
6+
http://www.springframework.org/schema/integration/redis https://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd">
97

10-
<util:constant id="redisConnectionFactory"
11-
static-field="org.springframework.integration.redis.rules.RedisAvailableRule.connectionFactory"/>
8+
<bean id="redisConnectionFactory" class="org.springframework.integration.redis.RedisTest"
9+
factory-method="connectionFactory"/>
1210

1311
<int-redis:publish-subscribe-channel id="redisChannel" topic-name="si.test.topic.parser"
1412
serializer="redisSerializer"/>

0 commit comments

Comments
 (0)