Как правило, когда вы используете в Spring Framework тестах какие-то специфичные properties, у вас есть несколько вариантов чтобы задать их значения:
application.properties
вы можете использовать файл application.properties
в тестовых ресурсах, тогда эти настройки
будут применены ко всем тестовым сценариям, которые вы пишите:
sprint.redis.host = 127.0.0.1
spring.redis.port = 15092
TestPropertySource
можно указать значение свойств прямо в тест-кейсе при помощи аннотации @TestPropertiesSource
,
которая делает inline значения, указаного в ней:
@SpringBootTest
@ExtendWith(SpringExtension.class)
@TestPropertySource(properties = "spring.redis.host=127.0.0.1")
class SomeRedisTest {
...
}
Проблема
А что делать, если значение, которое вам нужно задать - не константа?
В нашем примере выше хост и порт, на котором стартует redis, могут быть нам не известны
до того как мы запустим тест. В такой ситуации легко оказаться, если вы используете TestContainers для запуска redis в тестах.
До того как будет запущен контейнер, вы не сможете указать порт для теста.
Таким образом, вариант использования application.properties
отпадает автоматически,
как и аннотация @TestPropertySource
, потому что в java-аннотации можно указать только константное значение.
Из-за таких особенностей приходится создавать в тестах свои ApplicationContextInitializer-ы или инициализировать эти свойства в static инициализаторах:
@Testcontainers
@ContextConfiguration(initializers = RedisIntegrationTest.CustomInitializer.class)
@DataRedisTest
public class RedisIntegrationTest {
@Container
public static RedisContainer redis = new RedisContainer();
static class CustomInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(
ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of("spring.redis.port=" + redis.getMappedPort())
.applyTo(configurableApplicationContext.getEnvironment());
}
}
}
Аннотация @Testcontainers
работает при помощи расширения для junit5,
которое ищет контейнеры (помеченные аннотацией @Container
) в коде теста
и запускает их.
Конечно, можно было сделать решение, заточенное специально для этой задачи, и добавить какое-нибудь расширение, стартующее redis-контейнер и проставляющее host/port, на котором он запустился, перед запуском теста. Честно говоря, я так и сделал, когда первый раз столкнулся с этой задачей, пару месяцев назад (что из этого получилось можно посмотреть тут: github.com/jupiter-tools/spring-test-redis), но ведь можно сделать что-то более универсальное.
Решение
В результате у меня получилось сделать механизм,
позволяющий пометить аннотацией @DynamicTestProperty
метод,
добавляющий новые значения переменных окружения для теста:
@SpringBootTest
@Testcontainers
@ExtendWith(SpringExtension.class)
class RedisTestcontainersTest {
private static final Integer REDIS_PORT = 6379;
@Container (1)
private static GenericContainer redis = new GenericContainer("redis:latest")
.withExposedPorts(REDIS_PORT);
@DynamicTestProperty (2)
private static TestPropertyValues props() {
return TestPropertyValues.of("spring.redis.host=" + redis.getContainerIpAddress(),
"spring.redis.port=" + redis.getMappedPort(REDIS_PORT));
}
@Autowired
private RedisTemplate redisTemplate; (3)
@Test
void readWriteValueByRedisTemplate() {
String key = "test";
String value = "sabracadabra";
// Act
redisTemplate.opsForValue().set(key, value);
// Assert
assertThat(redisTemplate.opsForValue().get(key)).isEqualTo(value);
}
}
1 | объявляем контейнер для redis |
2 | прописываем хост/порт, на котором стартовал контейнер в стандартные спринговые настройки |
3 | используем в тесте redis, запущенный в тест-контейнере |
И чтобы все это работало в ваших spring-boot проектах, нужна всего лишь одна зависимость:
<dependency>
<groupId>com.jupiter-tools</groupId>
<artifactId>spring-dynamic-property</artifactId>
<version>0.1</version>
</dependency>
GitHub репозиторий
Больше подробностей вы можете прочитать на странице проекта.
Не стесняйтесь ставить звездочки на гитхабе =)