Пример
Если вы когда-нибудь работали с Hibernate, то почти наверняка знаете что для того чтобы посмотреть запросы, которые уходят в базу, нужно включить логирование в настройках. И это далеко не одна настройка, их нужно прописать порядка десяти штук.
В итоге наш application.properties для тестов выглядит примерно так:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.hibernate.type=TRACE
logging.level.org.hibernate.type.descriptor.sql=TRACE
logging.level.org.springframework.jdbc.datasource.init.ScriptUtils=TRACE
logging.level.org.hibernate.event.internal=TRACE
logging.level.org.hibernate.stat=TRACE
Каждый раз, когда приходится вспоминать эти настройки, я прохожу 5 стадий принятия неизбежного и ищу, из какого проекта их скопировать побыстрее.
Идея
В очередной раз, делая это, я подумал что было бы неплохо завернуть все эти настройки в одну аннотацию, например так:
@TraceSql (1)
@DataJpaTest
public class DataJpaTest {
...
}
1 | TraceSql включает логирование SQL запросов, выполняемых в тесте |
На первый взгляд, сделать это довольно не сложно,
можно воспользоваться @TestPropertySource
и продекларировать
все что нам нужно:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@TestPropertySource(properties = {
"spring.jpa.show-sql=true",
"spring.jpa.properties.hibernate.format_sql=true",
"spring.jpa.properties.hibernate.use_sql_comments=true",
"logging.level.org.hibernate.type.descriptor.sql=TRACE",
"logging.level.org.springframework.jdbc.datasource.init.ScriptUtils=TRACE",
"logging.level.org.hibernate.event.internal=TRACE"
})
public @interface TraceSql {
}
все это даже будет работать, мы увидим в логах запросы и много полезной информации от hibernate:
Hibernate:
select
box0_.id as id1_0_0_,
box0_.color as color2_0_0_
from
box box0_
where
box0_.id=?
2019-06-02 11:45:22.810 binding parameter [1] as [VARCHAR] - [bf71b8d4-c22c-4223-82c7-28900ae64abd]
...
...
Проблема
Этот подход работает до того момента, когда нам захочется определить
в тесте еще и локальные настройки переменных окружения, через TestPropertySource
:
@TraceSql
@DataJpaTest
@TestPropertySource(properties = "my.local.property=123")
public class DataJpaTest {
...
}
И проблема тут в том, как Spring Framework сканирует аннотации TestPropertySource
во время построения контекста приложения.
Там была учтена возможность использовать эту аннотацию
в родительском классе, а так же внутри своей композитной мета-аннотации, но вся эта схема не
была рассчитана на то, что мы будем использовать несколько аннотаций одновременно (одну локально
над тестовым классом, а вторую внутри своей мета-аннотации).
Возможность указывать несколько различных значений, путем повторения одной и той же аннотации принято называть
свойством repeatable для мета-аннотаций. Собственно, проблема в том, что TestPropertySource
не repeatable.
Как решить проблему
Сначала я подумал, что можно сделать что-то свое или даже применить DynamicTestProperty
(описаные в предыдущей статье How to set dynamic value of properties in Spring).
Но потом в голову пришло единственное правильное решение - Надо исправить проблему в самом фреймворке!
Тем более, немного погуглив, я понял, что не только мне нужно это решение.
В общем, на выходных я нашел немного времени, чтобы исправить эту проблему и собрать пул-реквест. На прошлой неделе его благополучно слили в мастер-ветку(gh-23320) и теперь в Spring Framework 5.2 такой проблемы уже не будет.
Надо сказать большое спасибо комьюнити Spring Framework за такую оперативность и помощь.