![](/img/trans.png)
[英]JUnit 5 and inheritance with different extensions for Spring Boot Integration test with TestContainers
[英]How to connect to testcontainers redis correctly during spring boot integration test?
我正在为 spring 启动中的服务编写测试
@Component
public class MyService {
@Autowired
StringRedisTemplate stringRedisTemplate;
// a number of other @Autowired dependencies
public User getUser(String uuid) {
var key = String.format("user:%s", uuid);
var cache = stringRedisTemplate.opsForValue().get(key);
if (cache == null) {
// return user from database
} else {
// return user from deserialized cache
}
}
}
@Testcontainers
@SpringBootTest
class MyServiceTest {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
MyService myService;
@Container
public static GenericContainer<?> redis =
new GenericContainer<>("redis:5.0.14-alpine3.15").withExposedPorts(6379);
@BeforeClass
public static void startContainer() {
redis.start();
var redisUrl = String.format("redis://%s:%s", redis.getHost(), redis.getMappedPort(6379));
System.setProperty("spring.redis.url", redisUrl);
}
@AfterClass
public static void stopContainer() {
redis.stop();
}
@Test
void getUser_returnCachedUser() {
// breakpoint here
stringRedisTemplate.opsForValue().set("user:some-uuid", "{\"uuid\":\"some-uuid\",\"name\":\"cache\"}");
var user = myService.getUser("some-uuid");
assertEquals("cache", user.name);
}
}
当我在调试模式下运行它并打断点时,我注意到控制台中的端口redis.getMappedPort(6379)
不等于stringRedisTemplate.connectionFactory.client
或myService.stringRedisTemplate.connectionFactory.client
。
System.setProperty
是否覆盖了属性并在这种情况下生效? 如何在 spring 启动集成测试中使用测试容器?
我建议使用与构建在测试容器之上的playtika略有不同的容器。
您需要做的是在pom.xml
中包含spring-cloud-starter-bootstrap (作为测试依赖项就足够了)。
然后在您的测试application.yaml|properties
中使用以下内容:
spring:
redis:
port: ${embedded.redis.port}
password: ${embedded.redis.password}
host: ${embedded.redis.host}
ssl: false
您可以使用getFirstMappedPort()
而不是redis.getMappedPort(6379)
因为 testcontainer 使用随机端口。 6379是宿主机端口,但是redis容器的端口是随机分配的,避免冲突。 更多细节可以在另一个线程中找到: https ://stackoverflow.com/a/50869731
我在 Java 和 Kotlin 中准备了 2 个模板,它们对我有用(servlet 不是 webflux),供您参考。
// AbstractIntegrationTest.java
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
@SpringBootTest
@Testcontainers
@DirtiesContext
public abstract class AbstractIntegrationTest {
public static final GenericContainer<?> redis =
new GenericContainer<>(DockerImageName.parse("redis:5.0.14-alpine3.15"))
.withExposedPorts(6379);
public static final GenericContainer<?> mysql =
new GenericContainer<>(DockerImageName.parse("mysql:5.7.28"))
.withExposedPorts(3306)
.withEnv("MYSQL_ROOT_PASSWORD", "password")
.withEnv("MYSQL_USER", "test")
.withEnv("MYSQL_PASSWORD", "password")
.withEnv("MYSQL_DATABASE", "db_test");
static {
redis.start();
mysql.start();
}
@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.redis.url", () -> String.format(
"redis://%s:%s",
redis.getHost(),
redis.getMappedPort(6379)
));
registry.add("spring.datasource.url", () -> String.format(
"jdbc:mysql://%s:%s@%s:%s/%s",
"root",
"password",
mysql.getHost(),
mysql.getMappedPort(3306),
"db_test"
));
}
}
// MyServiceTest.java
@Testcontainers
@SpringBootTest
@DirtiesContext
class MyServiceTest extends AbstractIntegrationTest {
@Autowired
MyService myService;
@Test
void someMethod() {
// preparation
// invocation
myService.someMethod()
// assertion
}
}
这是 Kotlin 版本:
// AbstractIntegrationTest.kt
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.GenericContainer
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.utility.DockerImageName
@SpringBootTest
@Testcontainers
@DirtiesContext
abstract class AbstractIntegrationTest {
companion object {
@JvmStatic
val redis: GenericContainer<*> = GenericContainer(DockerImageName.parse("redis:5.0.14-alpine3.15"))
.withExposedPorts(6379)
@JvmStatic
val mysql: GenericContainer<*> = GenericContainer(DockerImageName.parse("mysql:5.7.28"))
.withExposedPorts(3306)
.withEnv("MYSQL_ROOT_PASSWORD", "password")
.withEnv("MYSQL_USER", "test")
.withEnv("MYSQL_PASSWORD", "password")
.withEnv("MYSQL_DATABASE", "db_test")
init {
redis.start()
mysql.start()
}
@DynamicPropertySource
@JvmStatic
fun registerProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.redis.url") {
String.format(
"redis://%s:%s",
redis.host,
redis.getMappedPort(6379)
)
}
registry.add("spring.datasource.url") {
String.format(
"jdbc:mysql://%s:%s@%s:%s/%s",
"root",
"password",
mysql.host,
mysql.getMappedPort(3306),
"db_test"
)
}
}
}
}
// MyServiceTest.kt
@SpringBootTest
internal class MyServiceTest : AbstractIntegrationTest() {
@Autowired
lateinit var myService: MyService
@Test
fun someMethod() {
// preparation
// invocation
myService.someMethod()
// assertion
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.