Пример

Если вы когда-нибудь работали с 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 стадий принятия неизбежного и ищу, из какого проекта их скопировать побыстрее.

5 stadi prinatia

Идея

В очередной раз, делая это, я подумал что было бы неплохо завернуть все эти настройки в одну аннотацию, например так:

@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 за такую оперативность и помощь.