Skip to content

Commit 8a3a601

Browse files
committed
Add Redis integration tests
Signed-off-by: Hyunwoo Jung <[email protected]>
1 parent 88d76d8 commit 8a3a601

File tree

5 files changed

+358
-0
lines changed

5 files changed

+358
-0
lines changed

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@
134134
<nashorn.version>15.6</nashorn.version>
135135
<beanshell.version>2.0b6</beanshell.version>
136136
<jruby.version>9.4.12.0</jruby.version>
137+
<lettuce.version>6.6.0.RELEASE</lettuce.version>
138+
<jedis.version>6.0.0</jedis.version>
139+
<testcontainers-redis.version>2.2.4</testcontainers-redis.version>
137140

138141
<!-- samples dependencies -->
139142
<spring-rabbit.version>${spring-amqp.version}</spring-rabbit.version>

spring-batch-infrastructure/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,24 @@
559559
<version>${jruby.version}</version>
560560
<scope>test</scope>
561561
</dependency>
562+
<dependency>
563+
<groupId>io.lettuce</groupId>
564+
<artifactId>lettuce-core</artifactId>
565+
<version>${lettuce.version}</version>
566+
<scope>test</scope>
567+
</dependency>
568+
<dependency>
569+
<groupId>redis.clients</groupId>
570+
<artifactId>jedis</artifactId>
571+
<version>${jedis.version}</version>
572+
<scope>test</scope>
573+
</dependency>
574+
<dependency>
575+
<groupId>com.redis</groupId>
576+
<artifactId>testcontainers-redis</artifactId>
577+
<version>${testcontainers-redis.version}</version>
578+
<scope>test</scope>
579+
</dependency>
562580

563581
<!-- provided dependencies -->
564582
<dependency>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.item.redis;
18+
19+
import com.redis.testcontainers.RedisContainer;
20+
import org.junit.jupiter.api.AfterEach;
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.Arguments;
25+
import org.junit.jupiter.params.provider.MethodSource;
26+
import org.springframework.batch.item.ExecutionContext;
27+
import org.springframework.batch.item.redis.example.Person;
28+
import org.springframework.data.redis.connection.RedisConnectionFactory;
29+
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
30+
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
31+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
32+
import org.springframework.data.redis.core.RedisTemplate;
33+
import org.springframework.data.redis.core.ScanOptions;
34+
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
35+
import org.springframework.data.redis.serializer.StringRedisSerializer;
36+
import org.springframework.test.context.junit.jupiter.SpringExtension;
37+
import org.testcontainers.junit.jupiter.Container;
38+
import org.testcontainers.junit.jupiter.Testcontainers;
39+
import org.testcontainers.utility.DockerImageName;
40+
41+
import java.util.ArrayList;
42+
import java.util.List;
43+
import java.util.stream.Stream;
44+
45+
import static org.hamcrest.MatcherAssert.assertThat;
46+
import static org.hamcrest.Matchers.containsInAnyOrder;
47+
48+
/**
49+
* @author Hyunwoo Jung
50+
*/
51+
@Testcontainers(disabledWithoutDocker = true)
52+
@ExtendWith(SpringExtension.class)
53+
class RedisItemReaderIntegrationTests {
54+
55+
private static final DockerImageName REDIS_IMAGE = DockerImageName.parse("redis:8.0.2");
56+
57+
@Container
58+
public static RedisContainer redis = new RedisContainer(REDIS_IMAGE);
59+
60+
private RedisItemReader<String, Person> reader;
61+
62+
private RedisTemplate<String, Person> template;
63+
64+
@BeforeEach
65+
void setUp() {
66+
this.template = setUpRedisTemplate(lettuceConnectionFactory());
67+
}
68+
69+
@AfterEach
70+
void tearDown() {
71+
this.template.getConnectionFactory().getConnection().serverCommands().flushAll();
72+
}
73+
74+
@ParameterizedTest
75+
@MethodSource("connectionFactories")
76+
void testRead(RedisConnectionFactory connectionFactory) throws Exception {
77+
this.template.opsForValue().set("person:1", new Person(1, "foo"));
78+
this.template.opsForValue().set("person:2", new Person(2, "bar"));
79+
this.template.opsForValue().set("person:3", new Person(3, "baz"));
80+
this.template.opsForValue().set("person:4", new Person(4, "qux"));
81+
this.template.opsForValue().set("person:5", new Person(5, "quux"));
82+
83+
RedisTemplate<String, Person> redisTemplate = setUpRedisTemplate(connectionFactory);
84+
ScanOptions scanOptions = ScanOptions.scanOptions().match("person:*").count(10).build();
85+
this.reader = new RedisItemReader<>(redisTemplate, scanOptions);
86+
87+
this.reader.open(new ExecutionContext());
88+
89+
List<Person> items = new ArrayList<>();
90+
for (int i = 0; i < 5; i++) {
91+
items.add(this.reader.read());
92+
}
93+
94+
assertThat(items, containsInAnyOrder(new Person(1, "foo"), new Person(2, "bar"), new Person(3, "baz"),
95+
new Person(4, "qux"), new Person(5, "quux")));
96+
}
97+
98+
private RedisTemplate<String, Person> setUpRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
99+
RedisTemplate<String, Person> redisTemplate = new RedisTemplate<>();
100+
redisTemplate.setConnectionFactory(redisConnectionFactory);
101+
redisTemplate.setKeySerializer(new StringRedisSerializer());
102+
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
103+
redisTemplate.afterPropertiesSet();
104+
105+
return redisTemplate;
106+
}
107+
108+
private static Stream<Arguments> connectionFactories() {
109+
return Stream.of(Arguments.of(lettuceConnectionFactory()), Arguments.of(jedisConnectionFactory()));
110+
}
111+
112+
private static RedisConnectionFactory lettuceConnectionFactory() {
113+
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(
114+
new RedisStandaloneConfiguration(redis.getRedisHost(), redis.getRedisPort()));
115+
lettuceConnectionFactory.afterPropertiesSet();
116+
return lettuceConnectionFactory;
117+
}
118+
119+
private static JedisConnectionFactory jedisConnectionFactory() {
120+
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(
121+
new RedisStandaloneConfiguration(redis.getRedisHost(), redis.getRedisPort()));
122+
jedisConnectionFactory.afterPropertiesSet();
123+
return jedisConnectionFactory;
124+
}
125+
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.item.redis;
18+
19+
import com.redis.testcontainers.RedisContainer;
20+
import org.junit.jupiter.api.AfterEach;
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
import org.springframework.batch.item.Chunk;
28+
import org.springframework.batch.item.redis.example.Person;
29+
import org.springframework.data.redis.connection.RedisConnectionFactory;
30+
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
31+
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
32+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
33+
import org.springframework.data.redis.core.RedisTemplate;
34+
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
35+
import org.springframework.data.redis.serializer.StringRedisSerializer;
36+
import org.springframework.test.context.junit.jupiter.SpringExtension;
37+
import org.testcontainers.junit.jupiter.Container;
38+
import org.testcontainers.junit.jupiter.Testcontainers;
39+
import org.testcontainers.utility.DockerImageName;
40+
41+
import java.util.stream.Stream;
42+
43+
import static org.junit.jupiter.api.Assertions.*;
44+
45+
/**
46+
* @author Hyunwoo Jung
47+
*/
48+
@Testcontainers(disabledWithoutDocker = true)
49+
@ExtendWith(SpringExtension.class)
50+
class RedisItemWriterIntegrationTests {
51+
52+
private static final DockerImageName REDIS_IMAGE = DockerImageName.parse("redis:8.0.2");
53+
54+
@Container
55+
public static RedisContainer redis = new RedisContainer(REDIS_IMAGE);
56+
57+
private RedisItemWriter<String, Person> writer;
58+
59+
private RedisTemplate<String, Person> template;
60+
61+
@BeforeEach
62+
void setUp() {
63+
this.template = setUpRedisTemplate(lettuceConnectionFactory());
64+
}
65+
66+
@AfterEach
67+
void tearDown() {
68+
this.template.getConnectionFactory().getConnection().serverCommands().flushAll();
69+
}
70+
71+
@ParameterizedTest
72+
@MethodSource("connectionFactories")
73+
void testWriteWithLettuce(RedisConnectionFactory connectionFactory) throws Exception {
74+
RedisTemplate<String, Person> redisTemplate = setUpRedisTemplate(connectionFactory);
75+
this.writer = new RedisItemWriter<>();
76+
this.writer.setRedisTemplate(redisTemplate);
77+
this.writer.setItemKeyMapper(p -> "person:" + p.getId());
78+
this.writer.setDelete(false);
79+
80+
Chunk<Person> items = new Chunk<>(new Person(1, "foo"), new Person(2, "bar"), new Person(3, "baz"),
81+
new Person(4, "qux"), new Person(5, "quux"));
82+
this.writer.write(items);
83+
84+
assertEquals(new Person(1, "foo"), this.template.opsForValue().get("person:1"));
85+
assertEquals(new Person(2, "bar"), this.template.opsForValue().get("person:2"));
86+
assertEquals(new Person(3, "baz"), this.template.opsForValue().get("person:3"));
87+
assertEquals(new Person(4, "qux"), this.template.opsForValue().get("person:4"));
88+
assertEquals(new Person(5, "quux"), this.template.opsForValue().get("person:5"));
89+
}
90+
91+
@ParameterizedTest
92+
@MethodSource("connectionFactories")
93+
void testDelete(RedisConnectionFactory connectionFactory) throws Exception {
94+
this.template.opsForValue().set("person:1", new Person(1, "foo"));
95+
this.template.opsForValue().set("person:2", new Person(2, "bar"));
96+
this.template.opsForValue().set("person:3", new Person(3, "baz"));
97+
this.template.opsForValue().set("person:4", new Person(4, "qux"));
98+
this.template.opsForValue().set("person:5", new Person(5, "quux"));
99+
100+
RedisTemplate<String, Person> redisTemplate = setUpRedisTemplate(connectionFactory);
101+
this.writer = new RedisItemWriter<>();
102+
this.writer.setRedisTemplate(redisTemplate);
103+
this.writer.setItemKeyMapper(p -> "person:" + p.getId());
104+
this.writer.setDelete(true);
105+
106+
Chunk<Person> items = new Chunk<>(new Person(1, "foo"), new Person(2, "bar"), new Person(3, "baz"),
107+
new Person(4, "qux"), new Person(5, "quux"));
108+
this.writer.write(items);
109+
110+
assertFalse(this.template.hasKey("person:1"));
111+
assertFalse(this.template.hasKey("person:2"));
112+
assertFalse(this.template.hasKey("person:3"));
113+
assertFalse(this.template.hasKey("person:4"));
114+
assertFalse(this.template.hasKey("person:5"));
115+
}
116+
117+
private RedisTemplate<String, Person> setUpRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
118+
RedisTemplate<String, Person> redisTemplate = new RedisTemplate<>();
119+
redisTemplate.setConnectionFactory(redisConnectionFactory);
120+
redisTemplate.setKeySerializer(new StringRedisSerializer());
121+
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
122+
redisTemplate.afterPropertiesSet();
123+
124+
return redisTemplate;
125+
}
126+
127+
private static Stream<Arguments> connectionFactories() {
128+
return Stream.of(Arguments.of(lettuceConnectionFactory()), Arguments.of(jedisConnectionFactory()));
129+
}
130+
131+
private static RedisConnectionFactory lettuceConnectionFactory() {
132+
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(
133+
new RedisStandaloneConfiguration(redis.getRedisHost(), redis.getRedisPort()));
134+
lettuceConnectionFactory.afterPropertiesSet();
135+
return lettuceConnectionFactory;
136+
}
137+
138+
private static JedisConnectionFactory jedisConnectionFactory() {
139+
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(
140+
new RedisStandaloneConfiguration(redis.getRedisHost(), redis.getRedisPort()));
141+
jedisConnectionFactory.afterPropertiesSet();
142+
return jedisConnectionFactory;
143+
}
144+
145+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.item.redis.example;
18+
19+
import java.io.Serial;
20+
import java.io.Serializable;
21+
import java.util.Objects;
22+
23+
/**
24+
* @author Hyunwoo Jung
25+
*/
26+
public class Person implements Serializable {
27+
28+
@Serial
29+
private static final long serialVersionUID = 2396556853218591048L;
30+
31+
private long id;
32+
33+
private String name;
34+
35+
public Person(long id, String name) {
36+
this.id = id;
37+
this.name = name;
38+
}
39+
40+
public long getId() {
41+
return id;
42+
}
43+
44+
public String getName() {
45+
return name;
46+
}
47+
48+
@Override
49+
public boolean equals(Object o) {
50+
if (o == null || getClass() != o.getClass())
51+
return false;
52+
Person person = (Person) o;
53+
return id == person.id && Objects.equals(name, person.name);
54+
}
55+
56+
@Override
57+
public int hashCode() {
58+
return Objects.hash(id, name);
59+
}
60+
61+
@Override
62+
public String toString() {
63+
return "Person{id=" + id + ", name=" + name + "}";
64+
}
65+
66+
}

0 commit comments

Comments
 (0)