Кожен TestContext
забезпечує керування контекстом та підтримку кешування для екземпляра тесту, за який
він відповідає. Тестові екземпляри не отримують доступ до налаштованого ApplicationContext
автоматично.
Проте, якщо тестовий клас реалізує інтерфейс ApplicationContextAware
, посилання на ApplicationContext
передається тестовому екземпляру. Зверни увагу, що AbstractJUnit4SpringContextTests
та AbstractTestNGSpringContextTests
реалізують ApplicationContextAware
і, отже, забезпечують доступ до ApplicationContext
автоматично.
В якості альтернативи реалізації інтерфейсу ApplicationContextAware
можна впровадити контекст
програми для твого тестового класу через анотацію @Autowired
для поля або сетера, як показано в
наступному прикладі:
@SpringJUnitConfig
class MyTest {
@Autowired
ApplicationContext applicationContext;
// тіло класу...
}
- Впроваджуємо
ApplicationContext
.
@SpringJUnitConfig
class MyTest {
@Autowired
lateinit var applicationContext: ApplicationContext
// тіло класу...
}
- Впроваджуємо
ApplicationContext
.
Аналогічно, якщо тест налаштований на завантаження WebApplicationContext
, то можна впровадити
контекст вебпрограми до тесту таким чином:
@SpringJUnitWebConfig
class MyWebAppTest {
@Autowired
WebApplicationContext wac;
// тіло класу...
}
- Конфігуруємо
WebApplicationContext
. - Впроваджуємо
WebApplicationContext
.
@SpringJUnitWebConfig
class MyWebAppTest {
@Autowired
lateinit var wac: WebApplicationContext
// тіло класу...
}
- Конфігуруємо
WebApplicationContext
. - Впроваджуємо
WebApplicationContext
.
Впровадження залежностей за допомогою анотації @Autowired
забезпечується слухачем DependencyInjectionTestExecutionListener
,
який налаштований за замовчуванням ( див. "Впровадження залежностей для
тестових стендів").
Тестовим класам, які використовують фреймворк TestContext, не потрібно
розширювати будь-який конкретний клас або реалізовувати певний інтерфейс для конфігурування свого контексту
програми. Натомість конфігурація здійснюється шляхом оголошення анотації @ContextConfiguration
на рівні
класу. Якщо тестовий клас явно не оголошує розташування ресурсів контексту програми або компонентні класи,
налаштований завантажувач ContextLoader
визначає, як завантажити контекст із розташування за
замовчуванням або класів конфігурації за замовчуванням. На додаток до розташування ресурсів контексту та
компонентних
класів, контекст програми також може бути налаштований за допомогою ініціалізаторів контексту програми.
У
наступних розділах пояснюється, як використовувати анотацію @ContextConfiguration
у Spring для
налаштування тестового ApplicationContext
за допомогою конфігураційних XML-файлів, скриптів Groovy,
компонентних класів (зазвичай класів з анотацією @Configuration
) або ініціалізаторів контексту. До
того ж, можна реалізувати та конфігурувати власний SmartContextLoader
для розширених випадків
використання.
Конфігурація контексту з використанням ресурсів XML
Щоб завантажити ApplicationContext
для ваших тестів за допомогою конфігураційних XML-файлів, анотуй тестовий клас за допомогою @ContextConfiguration
і сконфігуруй атрибут locations
за допомогою масиву, який містить дані про розташування ресурсів
конфігураційних метаданих XML. Звичайний або відносний шлях (наприклад, context.xml
) розглядається як
ресурс classpath, що належить до пакета, в якому визначено тестовий клас. Шлях, що починається з косої риси,
вважається абсолютним розташуванням classpath (наприклад, /org/example/config.xml
). Шлях, що
представляє URL ресурсу (тобто шлях з префіксами classpath:
, file:
, http:
і
т.д.), використовується як є.
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з "/app-config.xml" та
// "/test-config.xml" в корені шляху класів
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"})
class MyTest {
// тіло класу...
}
- Встановлюємо атрибут розташування у списку XML-файлів.
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з " /app-config.xml" та
// "/test-config.xml" в корені шляху класів
@ContextConfiguration("/app-config.xml", "/test-config.xml")
class MyTest {
// тіло класу...
}
- Встановлюємо атрибут розташування у списку XML-файлів.
Анотація @ContextConfiguration
підтримує псевдонім для атрибуту locations
через
стандартний атрибут value
з Java. Таким чином, якщо не потрібно оголошувати додаткові атрибути в
анотації @ContextConfiguration
, то можна опустити оголошення імені атрибута locations
і
оголосити розташування ресурсів, використовуючи скорочений формат, продемонстрований у наступному прикладі:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"})
class MyTest {
// тіло класу...
}
- Встановлюємо XML-файли без використання атрибуту
location
.
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-config.xml")
class MyTest {
// тіло класу...
}
- Встановлюємо XML-файли без використання атрибуту
location
.
Якщо ти опустиш атрибути location
та value
в анотації
@ContextConfiguration
, фреймворк TestContext спробує визначити розташування XML-ресурсу за
замовчуванням.
Зокрема, GenericXmlContextLoader
та GenericXmlWebContextLoader
визначають місцезнаходження
за замовчуванням на основі імені тестового класу. Якщо твій клас називається com.example.MyTest
, GenericXmlContextLoader
завантажує контекст програми з "classpath:com/example/MyTest-context.xml"
. У цьому прикладі показано,
як це зробити:
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з
// "classpath:com/example/ MyTest-context.xml"
@ContextConfiguration
class MyTest {
// тіло класу...
}
- Завантажуємо конфігурацію з розташування за замовчуванням.
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration
class MyTest {
// тіло класу...
}
- Завантажуємо конфігурацію з розташування за замовчуванням.
Конфігурація контексту за допомогою сценаріїв Groovy
Щоб завантажити ApplicationContext
для тестів за допомогою скриптів Groovy, які використовують Groovy Bean Definition
DSL.
Також ти можеш анотувати свій тестовий клас за допомогою @ContextConfiguration
і
налаштувати атрибут locations
або value
за допомогою масиву, що містить розташування
ресурсів скриптів Groovy. Семантика пошуку ресурсів для скриптів Groovy така сама, як і для конфігураційних
XML-файлів.
ApplicationContext
у Spring TestContext
Framework активується автоматично, якщо Groovy знаходиться в classpath.
У наступному прикладі показано, як задати конфігураційні файли Groovy:
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з "/AppConfig.groovy" та
// "/TestConfig.groovy" в корені шляху класів
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"})
class MyTest {
// тіло класу...
}
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з "/AppConfig.groovy" і
// "/TestConfig.groovy" в корені шляху класів
@ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy" )
class MyTest {
// тіло класу...
}
- Визначення місцезнаходження файлів Groovy.
Якщо ти опустиш атрибути locations
і value
з анотації @ContextConfiguration
, фреймворк TestContext спробує визначити скрипт Groovy за
замовчуванням.
Зокрема, GenericGroovyXmlContextLoader
та GenericGroovyXmlWebContextLoader
визначають
місцезнаходження за замовчуванням на основі імені тестового класу. Якщо твій клас має ім'я
com.example.MyTest
, завантажувач контексту Groovy завантажить контекст вашої програми з "classpath:com/example/MyTestContext.groovy"
.
У цьому прикладі показано, як використовувати значення за замовчуванням:
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з
// "classpath:com/ example/MyTestContext.groovy"
@ContextConfiguration
class MyTest {
// тіло класу...
}
- Завантажуємо конфігурацію з розташування за замовчуванням.
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration
class MyTest {
// тіло класу...
}
- Завантажуємо конфігурацію з розташування за замовчуванням.
Ти можеш оголосити конфігураційні XML-файли та скрипти Groovy одночасно, використовуючи атрибут
location
або value
в анотації @ContextConfiguration
. Якщо шлях до
розташування конфігурованого ресурсу закінчується .xml
, він завантажується за допомогою XmlBeanDefinitionReader
.
В іншому випадку він завантажується за допомогою GroovyBeanDefinitionReader
.
У наступному лістингу показано, як об'єднати обидва варіанти в інтеграційному тесті:
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з
// "/app-config.xml" та "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/ TestConfig.groovy" })
class MyTest {
// тіло класу...
}
@ExtendWith (SpringExtension::class)
// ApplicationContext буде завантажено з
// "/app-config.xml" та "/TestConfig.groovy"
@ContextConfiguration("/app-config.xml", "/TestConfig.groovy")
class MyTest {
// тіло класу...
}
Конфігурація контексту з використанням компонентних класів
Щоб завантажити
ApplicationContext
для тестів за допомогою компонентних класів (див. розділ "Конфігурація контейнера на базі
Java"), можна анотувати свій тестовий клас за допомогою @ContextConfiguration
та налаштувати
атрибут classes
за допомогою масиву, що містить посилання на компонентні класи. У цьому прикладі
показано, як це зробити:
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з AppConfig та TestConfig
@ContextConfiguration(classes = { AppConfig.class, TestConfig.class})
class MyTest {
// тіло класу...
}
- Зазначення компонентних класів.
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з AppConfig та TestConfig
@ContextConfiguration(classes = [AppConfig::class, TestConfig::class])
class MyTest {
// тіло класу...
}
- Зазначення компонентних класів.
Термін "компонентний клас" може стосуватися будь-якого з наступних:
Клас, анотований за допомогою
@Configuration
.Компоненту (тобто класу, позначеному інструкціями
@Component
,@Service
,@Repository
або іншими стереотипними анотаціями).Класу, що відповідає стандарту JSR-330, анотованого за допомогою анотацій
javax.inject
.Будь-якому класу, що містить метод, анотований
@Bean
.Будь-якому іншому класу, який призначений для реєстрації як компонент Spring (тобто. бін Spring у
ApplicationContext
), що потенційно використовує переваги автоматичного виявлення та зв'язування єдиного конструктора без використання анотацій у Spring.
Дивіться javadoc по @Configuration
та @Bean
для отримання додаткової інформації про конфігурацію та семантику
компонентних класів, звертаючи особливу увагу на розгляд анотування за допомогою @Bean
у
полегшеному режимі.
Якщо ти опустиш атрибут classes
в анотації @ContextConfiguration
, фреймворк
TestContext спробує визначити наявність конфігураційних класів за замовчуванням. Зокрема, завантажувачі AnnotationConfigContextLoader
та AnnotationConfigWebContextLoader
виявляють усі статичні вкладені класи тестового класу, які
відповідають вимогам до реалізації конфігураційного класу, як зазначено в javadoc @Configuration
. Зверни увагу, що ім'я класу конфігурації є
довільним. До того ж, за бажанням тестовий клас може містити більше одного статичного вкладеного
конфігураційного класу. В наступному прикладі клас OrderServiceTest
оголошує статичний вкладений
конфігураційний клас з іменем Config
, який автоматично використовується для завантаження ApplicationContext
для тестового класу:
@SpringJUnitConfig
// ApplicationContext буде завантажений з
// Статичний вкладений клас Config
class OrderServiceTest {
@Configuration
static class Config {
// цей бін буде впроваджений в клас OrderServiceTest
@Bean OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// задаємо властивості тощо.
return orderService;
}
}
@Autowired
OrderService orderService;
@Test
void testOrderService() {
// тестуємо orderService
}
}
- Завантаження інформації про конфігурацію з вкладеного класу
Config
.
@SpringJUnitConfig
// ApplicationContext буде завантажений з the nested Config class
class OrderServiceTest {
@Autowired
lateinit var orderService: OrderService
@Configuration
class Config {
// цей бін буде впроваджений в клас OrderServiceTest
@Bean
fun orderService(): OrderService {
// задаємо властивості і т.д.
return OrderServiceImpl()
}
}
@Test
fun testOrderService() {
// тестуємо orderService
}
}
- Завантаження інформації про конфігурацію з вкладеного класу
Config
.
Комбінування XML, скриптів Groovy та компонентних класів
Іноді бажано змішувати конфігураційні XML-файли, скрипти Groovy та компонентні класи (зазвичай це позначені
анотацією @Configuration
класи), щоб налаштувати ApplicationContext
для твоїх тестів.
Наприклад, якщо ти використовуєш конфігурацію XML у виробничому середовищі, то можеш вирішити, що варто
вдатися до класів, помічених анотацією @Configuration
, щоб налаштувати певні компоненти, керовані
Spring, для твоїх тестів, або навпаки.
Ба більше, деякі сторонні фреймворки (наприклад, Spring Boot) надають відмінні засоби підтримки одночасного
завантаження ApplicationContext
з різних типів ресурсів (наприклад, конфігураційних XML-файлів,
скриптів Groovy та класів з анотацією) @Configuration
). Spring Framework історично не підтримував
це для стандартних розгортань. Отже, більшість реалізацій SmartContextLoader, які Spring Framework поставляє в
модулі spring-test, підтримують лише один тип ресурсів для кожного тестового контексту. Однак це не означає, що
не можна використовувати обидва варіанти. Винятком із загального правила є те, що GenericGroovyXmlContextLoader
та GenericGroovyXmlWebContextLoader
підтримують одночасно конфігураційні XML-файли та скрипти
Groovy. До того ж, сторонні фреймворки можуть підтримувати оголошення як locations
, так і
classes
через анотацію @ContextConfiguration
, а за стандартної підтримки тестування у фреймворку
TestContext у тебе є можливості, описані нижче.
Якщо необхідно використовувати розташування ресурсів (наприклад, XML або Groovy) та класи, анотовані @Configuration
,
щоб налаштувати ваші тести, тобі потрібно обрати щось із них за точку входу, і цей обраний клас або місце
розташування повинні включати або імпортувати інші. Наприклад, у XML або скрипти Groovy можна включити класи,
помічені анотацією @Configuration
, використовуючи сканування компонентів або визначаючи їх як
звичайні біни Spring, у той час як у класі, анотованому @Configuration
, можна використовувати
анотацію @ImportResource
, щоб імпортувати конфігураційні XML-файли або скрипти Groovy. Зверни
увагу, що така логіка роботи семантично еквівалентна тому, як ти конфігуруєш свою програму у виробничому
середовищі: у виробничій конфігурації ти визначаєш або набір ресурсів XML або Groovy, або набір класів,
позначених анотацією@Configuration
, з яких завантажується ваш виробничий
ApplicationContext
, однак у тебе все ще є можливість включати або імпортувати інший тип
конфігурації.
Конфігурація контекстів за допомогою ініціалізаторів контексту
Щоб налаштувати ApplicationContext
для твоїх тестів за допомогою ініціалізаторів контексту,
анотуй тестовий клас за допомогою @ContextConfiguration
і конфігуруй атрибут initializers
за допомогою масиву, що містить посилання на класи, які реалізують ApplicationContextInitializer
.
Оголошені ініціалізатори контексту використовуються для ініціалізації
ConfigurableApplicationContext
, який завантажується для ваших тестів. Зверніть увагу, що конкретний
тип ConfigurableApplicationContext
, що підтримується кожним оголошеним ініціалізатором, повинен
бути сумісним з типом ApplicationContext
, який створює SmartContextLoader
(зазвичай це
GenericApplicationContext
). Більш того, порядок виклику ініціалізаторів залежить від того, чи
реалізують вони інтерфейс Ordered
зі Spring або чи позначені вони анотацією @Order
або
стандартною анотацією @Priority
. У цьому прикладі показано, як використовувати ініціалізатори:
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з TestConfig
// та ініціалізований за допомогою TestAppCtxInitializer
@ContextConfiguration(
classes = TestConfig.class,
initializers = TestAppCtxInitializer.class)
class MyTest {
// тіло класу...
}
- Завдання конфігурації за допомогою конфігураційного класу та ініціалізатора.
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з TestConfig
// та ініціалізований за допомогою TestAppCtxInitializer
@ContextConfiguration(
classes = [TestConfig::class],
initializers = [TestAppCtxInitializer::class ])
class MyTest {
// тіло класу...
}
- Встановлення конфігурації за допомогою конфігураційного класу та ініціалізатора.
Також можна повністю опустити оголошення конфігураційних XML-файлів, скриптів Groovy або компонентних класів в
анотації @ContextConfiguration
і натомість оголосити тільки класи
ApplicationContextInitializer
,
які потім відповідатимуть за реєстрацію бінів у контексті – наприклад, шляхом програмного завантаження визначень
бінів із XML-файлів чи конфігураційних класів. У цьому прикладі показано, як це зробити:
@ExtendWith(SpringExtension.class)
// ApplicationContext буде ініціалізований за допомогою EntireAppInitializer
// який, як передбачається, реєструє біни в контексті
@ContextConfiguration(initializers = EntireAppInitializer.class)
class MyTest {
// тіло класу...
}
- Встановлення конфігурації за допомогою лише ініціалізатора.
@ExtendWith(SpringExtension::class)
// ApplicationContext буде ініціалізований за допомогою EntireAppInitializer
// який, як передбачається, реєструє біни в контексті
@ContextConfiguration(initializers = [EntireAppInitializer::class])
class MyTest {
// тіло класу...
}
- Встановлення конфігурації за допомогою лише ініціалізатора.
Спадкування конфігурації контексту
Анотація @ContextConfiguration
підтримує булеви атрибути inheritLocations
та inheritInitializers
,
які позначають, чи слід успадковувати розташування ресурсів або компонентні класи та ініціалізатори контексту,
оголошені суперкласами. За замовчуванням значення для обох прапорів є true
. Це означає, що тестовий
клас
успадковує розташування ресурсів або компонентні класи, а також ініціалізатори контексту, оголошені будь-якими
суперкласами. Зокрема, розташування ресурсів або компонентні класи для тестового класу додаються до списку
розташування ресурсів або анотованих класів, оголошених суперкласами. Так само ініціалізатори для даного
тестового класу додаються до набору ініціалізаторів, визначених суперкласами тестів. Таким чином, підкласи
можуть розширювати розташування ресурсів, компонентні класи або ініціалізатори контексту.
Якщо атрибут inheritLocations
або inheritInitializers
в анотації @ContextConfiguration
встановлений у false
, то розташування ресурсів або компонентні класи та ініціалізатори контексту
для тестового класу, відповідно, "затіняють" і ефективно замінюють конфігурацію, визначену суперкласами.
@Nested
".У наступному прикладі, в якому використовуються розташування XML-ресурсів, ApplicationContext
для
ExtendedTest
завантажується з base-config.xml
та extended-config.xml
саме в такому порядку. Тому біни, визначені в extended-config.xml
, можуть перевизначати (тобто
замінювати) біни, визначені у base-config.xml
. У наступному прикладі показано, як один клас може
розширювати інший і використовувати як власний конфігураційний файл, так і конфігураційний файл суперкласу:
@ExtendWith(SpringExtension.class )
// ApplicationContext буде завантажений з "/base-config.xml"
// в корені classpath @ContextConfiguration("/base-config.xml")
class BaseTest {
// тіло класу...
}
// ApplicationContext буде завантажений з "/base-config.xml" та
// "/extended-config.xml" в корені classpath
@ContextConfiguration("/extended-config .xml")
class ExtendedTest extends BaseTest {
// тіло класу...
}
- Конфігураційний файл, визначений у суперкласі.
- Конфігураційний файл, визначений у підкласі.
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з "/base-config.xml"
// в корені classpath @ContextConfiguration("/base-config.xml ")
open class BaseTest {
// тіло класу...
}
// ApplicationContext буде завантажений з "/base-config.xml" and
// "/extended-config.xml" в корені classpath
@ContextConfiguration("/extended-config.xml")
class ExtendedTest : BaseTest () {
// тіло класу...
}
- Конфігураційний файл, визначений у суперкласі.
- Конфігураційний файл, визначений у підкласі.
Точно так само в наступному прикладі, в якому використовуються компонентні класи,
ApplicationContext
для ExtendedTest
завантажується з класів BaseConfig
та
ExtendedConfig
саме в такому порядку. Тому біни, визначені в ExtendedConfig
, можуть
перевизначати (тобто замінювати) біни, визначені у BaseConfig
. В наступному прикладі показано, як один
клас може розширювати інший і використовувати як свій власний конфігураційний клас, так і клас класу конфігурації:
// ApplicationContext буде завантажений з BaseConfig
@SpringJUnitConfig(BaseConfig.class)
class BaseTest {
// тіло класу...
}
// ApplicationContext буде завантажений з BaseConfig і ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class)
class ExtendedTest extends BaseTest {
// тіло класу...
}
- Конфігураційний клас, визначений у суперкласі.
- Конфігураційний клас, визначений у підкласі.
// ApplicationContext буде завантажений з BaseConfig
@SpringJUnitConfig(BaseConfig::class)
open class BaseTest {
// тіло класу...
}
// ApplicationContext буде завантажено з BaseConfig та ExtendedConfig
@SpringJUnitConfig(ExtendedConfig::class)
class ExtendedTest : BaseTest() {
// тіло класу...
}
- Конфігураційний клас, визначений у суперкласі.
- Конфігураційний клас, визначений у підкласі.
У наступному прикладі, в якому використовуються ініціалізатори контексту, ApplicationContext
для
ExtendedTest
ініціалізується за допомогою BaseInitializer
та
ExtendedInitializer
. Зверни увагу, однак, що порядок виклику ініціалізаторів залежить від того, чи
реалізують вони інтерфейс Ordered
зі Spring або чи позначені вони анотацією @Order
з
Spring або стандартною анотацією @Priority
. У наступному прикладі показано, як один клас може
розширювати інший і використовувати як свій власний ініціалізатор, так і ініціалізатор суперкласу:
// ApplicationContext буде ініціалізований BaseInitializer
@SpringJUnitConfig (initializers = BaseInitializer.class)
class BaseTest {
// тіло класу...
}
// ApplicationContext буде ініціалізований BaseInitializer
// та ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class)
class ExtendedTest extends BaseTest {
// тіло класу...
}
- Ініціалізатор, визначений у суперкласі.
- Ініціалізатор, певний в підкласі.
// ApplicationContext буде ініціалізований BaseInitializer
@SpringJUnitConfig(initializers = [BaseInitializer::class])
open class BaseTest {
// тіло класу...
}
// ApplicationContext буде ініціалізований BaseInitializer
// та ExtendedInitializer
@SpringJUnitConfig(initializers = [ExtendedInitializer::class])
class ExtendedTest : BaseTest() {
// тіло класу...
}
- Ініціалізатор, визначений у суперкласі.
- Ініціалізатор, визначений у підкласі.
Конфігурація контексту з використанням профілів оточення
Spring Framework пропонує першокласну підтримку поняття середовищ та профілів (вони ж "профілі визначення
бінів"), а інтеграційні тести можна налаштувати на активацію певних профілів визначення бінів для різних
сценаріїв тестування. Це досягається шляхом анотування тестового класу анотацією @ActiveProfiles
та надання списку профілів, які потрібно активувати під час завантаження ApplicationContext
для
тесту.
@ActiveProfiles
з
будь-якою реалізацією SPI-інтерфейсу SmartContextLoader
, але анотація
@ActiveProfiles
не підтримується з реалізацією більше старого SPI-інтерфейсу ContextLoader
.
Розглянемо два приклади з XML-конфігурацією та класами, анотованими @Configuration
:
<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<bean id="transferService"
class="com.bank.service.internal.DefaultTransferService">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="feePolicy"/>
</bean>
<bean id="accountRepository"
class="com.bank.repository.internal.JdbcAccountRepository">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="feePolicy"
class="com.bank.service.internal.ZeroFeePolicy"/>
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script
location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
<beans profile="default">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
</jdbc:embedded-database>
</beans>
</beans>
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з " classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// тестуємо transferService
}
}
@ExtendWith(SpringExtension:: class)
// ApplicationContext буде завантажений з "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// тестуємо transferService
}
}
Коли виконується TransferServiceTest
, його ApplicationContext
завантажується
з конфігураційного файлу app-config.xml
в корені шляху класів. Якщо переглянути
app-config.xml
,
можна побачити, що бін AccountRepository
має залежність від біна DataSource
. Однак
dataSource
не визначено як високорівневий бін. Натомість dataSource
визначається
три рази: у профілі production
, у профілі dev
та у профілі default
.
Позначаючи TransferServiceTest
анотацією @ActiveProfiles("dev")
, ми даємо
Spring TestContext Framework команду завантажувати ApplicationContext
з активними профілями,
встановленими в {"dev"}
. В результаті створюється вбудована база даних, яка заповнюється
тестовими даними, а бін accountRepository
приєднується з посиланням на DataSource
розробки. Це, ймовірно, і є те, що потрібно отримати в інтеграційному тесті. Іноді доречним буде призначити
біни профілю default
. За замовчуванням вмикаються біни у профілі, лише якщо інший профіль
спеціально не був активований. Це можна використовувати для визначення бінів "повернення", які будуть
використовуватися у стані за замовчуванням. Наприклад, можна явно вказати джерело даних для профілів
dev
та production
, але визначити джерело даних у пам'яті як джерело за замовчуванням, якщо жоден з
них не активний.
У наступних лістингах коду продемонстровано, як реалізувати ту саму конфігурацію
та інтеграційний тест за допомогою класів з анотацією @Configuration
замість XML:
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("dev")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
@Configuration
public class TransferServiceConfig {
@Autowired DataSource dataSource;
@Bean
public TransferService transferService() {
return new DefaultTransferService(accountRepository(), feePolicy());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public FeePolicy feePolicy() {
return new ZeroFeePolicy();
}
}
@Configuration
class TransferServiceConfig {
@Autowired
lateinit var dataSource: DataSource
@Bean
fun transferService(): TransferService {
return DefaultTransferService(accountRepository(), feePolicy())
}
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun feePolicy(): FeePolicy {
return ZeroFeePolicy()
}
}
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// тестуємо transferService
}
}
@SpringJUnitConfig(
TransferServiceConfig::class,
StandaloneDataConfig::class,
JndiDataConfig::class,
DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// тестуємо transferService
}
}
У цьому варіанті ми розділили конфігурацію XML на чотири незалежні класи, позначені анотацією @Configuration
:
TransferServiceConfig
: ОтримуєdataSource
через впровадження залежностей за допомогою анотації@Autowired
.StandaloneDataConfig
: ВизначаєdataSource
для вбудованої бази даних, що підходить для тестів розробника.JndiDataConfig
: ВизначаєdataSource
, який витягується з JNDI у виробничому середовищі.DefaultDataConfig
: ВизначаєdataSource
для вбудованої бази даних за замовчуванням, якщо профіль не активний.
Як і в прикладі конфігурації на основі XML ми як і раніше анотуємо TransferServiceTest
за
допомогою анотації @ActiveProfiles("dev")
, але цього разу задаємо всі чотири класи конфігурації
за допомогою анотації @ContextConfiguration
. Тіло самого тестового класу залишається незмінним.
Часто буває так, що один набір профілів використовується в кількох тестових класах в рамках одного проєкту.
Таким чином, щоб уникнути дублювання оголошень анотації @ActiveProfiles
, можна оголосити
анотацію @ActiveProfiles
один раз для основного класу, а підкласи автоматично успадковуватимуть
конфігурацію з анотацією @ActiveProfiles
від основного класу. У наступному прикладі оголошення
анотації @ActiveProfiles
(а також інших анотацій) було перенесено до абстрактного суперкласу
AbstractIntegrationTest
:
@Nested
".
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
@SpringJUnitConfig(
TransferServiceConfig::class,
StandaloneDataConfig::class,
JndiDataConfig::class,
DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
// профіль "dev" успадковується від суперкласу
class TransferServiceTest extends AbstractIntegrationTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// тестуємо transferService
}
}
// профіль "dev" успадковується від суперкласу
class TransferServiceTest : AbstractIntegrationTest() {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// тестуємо transferService
}
}
Анотація @ActiveProfiles
також підтримує атрибут inheritProfiles
, який
можна використовувати для дезактивації успадкування активних профілів, як показано в наступному прикладі:
// профіль "dev" перевизначається на "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
// тіло тіста
}
// профіль "dev" перевизначається на "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
// тіло тесту
}
Більш того, іноді буває необхідно дозволити активні профілі для тестів програмно, а не декларативно — наприклад, виходячи з:
Поточної операційної системи.
Факта виконання тестів на сервері збірки для забезпечення безперервної інтеграції
Наявності певних змінних оточення.
Наявності власних анотацій на рівні класів.
Іншої наскрізної функціональності.
Щоб програмно дозволяти профілі визначення активних бінів, можна реалізувати користувальницький ActiveProfilesResolver
та
зареєструвати його за допомогою атрибуту resolver
в анотації @ActiveProfiles
. Для
отримання додаткових відомостей див. javadoc. У наступному прикладі продемонстровано, як реалізувати та
зареєструвати користувача OperatingSystemActiveProfilesResolver
:
// профіль "dev" перевизначається програмно за допомогою розпізнавача користувача
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// тіло тесту
}
// профіль "dev" перевизначається програмно за допомогою розпізнавача користувача
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver::class,
inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
// тіло тіста
}
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
String profile = ...;
// Визначаємо значення профілю, виходячи з операційної системи
return new String[] {profile};
}
}
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {
override fun resolve(testClass: Class<*>) : Array<String> {
val profile: String = ...
// визначаємо значення профілю, виходячи з операційної системи
return arrayOf(profile)
}
}
Конфігурація контексту з використанням джерел тестових властивостей
Spring Framework пропонує першокласні засоби підтримки поняття оточення з ієрархією джерел властивостей, тому
можна конфігурувати інтеграційні тести з використанням джерел тестових властивостей. На відміну від анотації
@PropertySource
, яка використовується в класах @Configuration
, анотацію @TestPropertySource
можна оголосити в тестовому класі, щоб оголошувати розташування ресурсів для тестових файлів. властивостей або
властивостей, що вбудовуються. Ці джерела тестових властивостей додаються до набору PropertySources
у Environment
для ApplicationContext
, завантаженого для анотованого інтеграційного
тесту.
Можна використовувати анотацію @TestPropertySource
з
будь-якою реалізацією SPI-інтерфейсу SmartContextLoader
, але анотація
@TestPropertySource
не підтримується при використанні більш старого SPI-інтерфейсу
ContextLoader
.
Реалізації SmartContextLoader
отримують доступ до значень об'єднаного джерела тестових
властивостей через методи getPropertySourceLocations()
та
getPropertySourceProperties()
у MergedContextConfiguration
.
Оголошення джерел тестових властивостей
Можна налаштувати файли тестових властивостей, використовуючи атрибут locations
або
value
в анотації @TestPropertySource
.
Підтримуються як традиційні, так і засновані на XML формати файлів властивостей — наприклад, "
classpath:/com/example/test.properties"
або "file:///path/to/file.xml"
.
Кожен шлях інтерпретується як Resource
зі Spring. Простий шлях (наприклад,
"test.properties"
) вважається ресурсом classpath, який належить до пакету, в якому визначено
тестовий клас. Шлях, що починається з косої риси, розглядається як абсолютний ресурс classpath (наприклад:
"/org/example/test.xml"
). Шлях, що посилається на URL-адресу (наприклад, шлях з префіксом classpath:
,
file:
або http:
), завантажується за допомогою зазначеного протоколу ресурсів.
Підстановочні знаки розташування ресурсів (наприклад, */.properties
) не допускаються: кожне місце
розташування повинно бути рівно одним ресурсом .properties
або .xml
.
У цьому прикладі використовується файл тестових властивостей:
@ContextConfiguration
@TestPropertySource("/test.properties")
class MyIntegrationTests {
// тіло класу...
}
- Вказуємо файл властивостей з абсолютним шляхом.
@ContextConfiguration
@TestPropertySource("/test.properties" )
class MyIntegrationTests {
// тіло класу...
}
- Вказуємо файл властивостей з абсолютним шляхом.
Можна конфігурувати вбудовані властивості у вигляді пар ключ-значення, використовуючи атрибут
properties
в анотації @TestPropertySource
, як показано в наведеному нижче прикладі. Всі
пари ключ-значення
додаються в об'ємну Environment
як один тестовий PropertySource
з найвищим рівнем
старшинства.
Підтримуваний синтаксис для пар ключ-значення такий самий, як та синтаксис, визначений для записів у файлі властивостей Java:
key=value
key:value
key value
У наступному прикладі зазначені дві властивості, що вбудовуються:
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"})
class MyIntegrationTests {
// тіло класу...
}
- Зазначення двох властивостей за допомогою двох варіантів синтаксису "ключ-значення".
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"])
class MyIntegrationTests {
// тіло класу...
}
- Зазначення двох властивостей за допомогою двох варіантів синтаксису "ключ-значення".
Починаючи з версії Spring Framework 5.2, анотацію @TestPropertySource
можна використовувати як анотацію, що повторюється. Це означає, що можна мати кілька оголошень анотації
@TestPropertySource
в одному тестовому класі, при цьому locations
та
properties
з наступних анотацій @TestPropertySource
перевизначатимуть властивості з
попередніх анотацій @TestPropertySource
.
Крім того, можна оголосити кілька складових анотацій для тестового класу, кожна з яких мета-анотована за
допомогою @ TestPropertySource
, і всі ці оголошення анотації @TestPropertySource
зроблять свій внесок до твоїх джерел тестових властивостей.
Анотації @TestPropertySource
, представлені безпосередньо, завжди мають старшість над
мета-поданими анотаціями @TestPropertySource
. Іншими словами, locations
та
properties
з безпосередньо присутньої анотації @TestPropertySource
перевизначатимуть
locations
та properties
з анотації @TestPropertySource
, яка
використовується як мета-анотації.
Виявлення файлу властивостей за замовчуванням
Якщо анотація @TestPropertySource
оголошена як порожня анотація (тобто без явних значень для
атрибутів locations
або properties
), робиться спроба виявити файл властивостей за
умовчанням щодо класу, який оголосив інструкцію. Наприклад, якщо анотований тестовий клас —
com.example.MyTest
, то відповідний файл властивостей за замовчуванням — classpath:com/example/MyTest.properties
.
Якщо значення за замовчуванням не може бути визначено, буде згенеровано виняток IllegalStateException
.
Старшинство
Тестові властивості мають
більш високий рівень старшинства, ніж властивості, визначені в середовищі операційної системи, системні властивості
Java або джерела властивостей, додані додатком декларативно за допомогою анотації @PropertySource
чи
програмно. Таким чином, тестові властивості можна використовувати для вибіркового перевизначення властивостей,
завантажених із джерел властивостей системи та програми. Крім того, вбудовувані властивості мають більш високий
рівень старшинства, ніж властивості, завантажені з розташування ресурсів. Зверни увагу, однак, що властивості,
зареєстровані через анотацію @DynamicPropertySource
, мають більш високий рівень старшинства, ніж
властивості, завантажені через анотацію @TestPropertySource
.
У наступному прикладі
властивості timezone
та port
, а також будь-які властивості, визначені у файлі "/test.properties"
,
перевизначають будь-які однойменні властивості, визначені у джерелах властивостей системи та додатка. Більше того,
якщо у файлі "/test.properties"
визначені записи для властивостей timezone
та
port
, то вони перевизначаються вбудованими властивостями, оголошеними за допомогою атрибуту properties
.
У наступному прикладі показано, як задати властивості як у файлі, так і вбудовано:
@ContextConfiguration
@TestPropertySource(
locations = "/test.properties",
properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
// тіло класу...
}
@ContextConfiguration
@TestPropertySource("/test.properties",
properties = ["timezone = GMT", "port: 4242"]
)
class MyIntegrationTests {
// class body...
}
Спадкування та перевизначення джерел тестових властивостей
Анотація @TestPropertySource
підтримує булеві
атрибутиinheritLocations
та inheritProperties
, які позначають, чи слід успадковувати
розташування ресурсів для файлів властивостей і вбудованих властивостей, оголошених суперкласами. За замовчуванням
для обох прапорів є true
. Це означає, що тестовий клас успадковує розташування та вбудовувані
властивості, оголошені будь-якими суперкласами. Зокрема, розташування та вбудовувані властивості для тестового
класу додаються до місць розташування та вбудовуваних властивостей, оголошених суперкласами. Таким чином,
підкласи можуть розширювати розташування та вбудовувані властивості. Зауважте, що властивості, які з'являються
пізніше, "затіняють" (тобто перевизначають) однойменні властивості, які з'являються раніше. До того ж,
згадані вище правила старшинства застосовуються і для успадкованих джерел тестових властивостей.
Якщо
атрибут inheritLocations
або inheritPropertie.s
в анотації
@TestPropertySource
встановлений у false
, то розташування або вбудовувані властивості для
тестового класу,
відповідно, "затіняють" і ефективно замінюють конфігурацію, визначену суперкласами.
@Nested
".
У наступному прикладі ApplicationContext
для BaseTest
завантажується лише за
допомогою файлу base.properties
як джерела тестових властивостей. В той же час
ApplicationContext
для ExtendedTest
завантажується з використанням файлів base.properties
та extended.properties
як джерела тестових властивостей. У наступному прикладі показано, як визначити
властивості як у підкласі, так і в його суперкласі за допомогою файлів properties
:
@TestPropertySource ("base.properties")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}
@TestPropertySource("base.properties")
@ContextConfiguration
open class BaseTest {
// ...
}
@TestPropertySource("extended.properties" )
@ContextConfiguration
class ExtendedTest : BaseTest() {
// ...
}
У наступному прикладі ApplicationContext
для BaseTest
завантажується тільки за
допомогою лише вбудованої властивості key1
. В той же час ApplicationContext
для ExtendedTest
завантажується за допомогою вбудованих властивостей key1
та key2
. У наступному прикладі
показано, як визначити властивості як у підкласі, так і в його суперкласі за допомогою вбудованих властивостей:
@TestPropertySource(properties = "key1 = value1 ")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}
@TestPropertySource(properties = ["key1 = value1"])
@ContextConfiguration
open class BaseTest {
// ...
}
@TestPropertySource(properties = [" key2 = value2"])
@ContextConfiguration
class ExtendedTest : BaseTest() {
// ...
}
Конфігурація контексту з використанням джерел динамічних властивостей
Починаючи зі Spring Framework 5.2.5, фреймворк TestContext забезпечує підтримку динамічних
властивостей за допомогою анотації @DynamicPropertySource
. Цю анотацію можна використовувати в
інтеграційних тестах, в яких необхідно додати властивості з динамічними значеннями в набір
PropertySources
у Environment
для ApplicationContext
, завантаженого для
інтеграційного тесту.
Аннотація @DynamicPropertySource
і підтримуюча її
інфраструктура спочатку були розроблені для того, щоб властивості з тестів, заснованих на Testcontainers, можна було легко відкрити для
інтеграційних тестів Spring. Однак цю функцію також можна використовувати з будь-якою формою зовнішнього ресурсу,
життєвий цикл якого підтримується поза ApplicationContext
тестом.
На відміну від анотації
@TestPropertySource
, яка застосовується на рівні класу, @DynamicPropertySource
слід
застосовувати до статичного методу, який приймає єдиний аргумент DynamicPropertyRegistry
, який
використовується для додавання пар "ім'я-значення" у Environment
. Значення є динамічними та
вказуються через Supplier
, який викликається лише при вирішенні властивості. Зазвичай для встановлення
значень використовуються посилання на методи, як видно з такого прикладу, в якому проєкт Testcontainers
використовується для управління контейнером Redis поза ApplicationContext
з Spring. IP-адреса та порт
керованого контейнера Redis доступні компонентам у ApplicationContext
тесту через властивості redis.host
та redis.port
. Доступ до цих властивостей можна отримати через абстракцію Environment
зі
Spring або впровадити безпосередньо до керованих Spring компонентів — наприклад, через анотації @Value("${redis.host}".)
і @Value("${redis.port}")
відповідно.
Якщо анотація @DynamicPropertySource
використовується в
основному класі та виявляється, що тести в підкласах не пройшли через те, що динамічні властивості змінюються
між підкласами, то може знадобитися анотувати основний клас за допомогою @DirtiesContext
щоб кожен
підклас гарантовано отримав свій власний ApplicationContext
з коректними динамічними властивостями.
@SpringJUnitConfig(/* . .. */)
@Testcontainers
class ExampleIntegrationTests {
@Container
static RedisContainer redis = new RedisContainer();
@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
registry.add("redis.host", redis::getContainerIpAddress);
registry.add("redis.port", redis::getMappedPort);
}
// тести...
}
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {
companion object {
@Container
@JvmStatic
val redis: RedisContainer = RedisContainer()
@DynamicPropertySource
@JvmStatic
fun redisProperties(registry: DynamicPropertyRegistry) {
registry.add("redis.host", redis::getContainerIpAddress)
registry.add("redis.port", redis::getMappedPort)
}
}
// тести...
}
Старшинство
Динамічні властивості мають більш високий рівень старшинства, ніж властивості,
завантажені з анотації @TestPropertySource
, оточення операційної системи, системних властивостей Java
або джерел властивостей, доданих додатком декларативно за допомогою анотації @PropertySource
або
програмно. Таким чином, динамічні властивості можна використовувати для вибіркового перевизначення властивостей,
завантажених через анотацію @TestPropertySource
, системні джерела властивостей та джерела властивостей
додатків.
Завантаження WebApplicationContext
Щоб дати фреймворку TestContext команду завантажувати
WebApplicationContext
замість стандартного ApplicationContext
, можна анотувати відповідний
тестовий клас за допомогою @WebAppConfiguration
.
Наявність анотації
@WebAppConfiguration
у твоєму тестовому класі вказує фреймворку TestContext (TCF), що для твоїх
інтеграційних тестів має бути завантажений WebApplicationContext
(WAC). У фоновому режимі TCF
забезпечує, що MockServletContext
буде створено та передано до WAC твого тесту. За замовчуванням шлях
до
базових ресурсів для MockServletContext
встановлений у src/main/webapp
. Він
інтерпретується як шлях щодо кореня вашої JVM (зазвичай це шлях до вашого проєкту). Якщо тобі знайома структура
каталогів вебпрограми в проєкті Maven, ти знаєш, що src/main/webapp
є за замовчуванням для кореня
твого WAR. Якщо потрібно перевизначити це значення за замовчуванням, можна вказати альтернативний шлях в анотації
@WebAppConfiguration
(наприклад, @WebAppConfiguration("src/test/webapp")
). Якщо потрібно
послатися на шлях до основного ресурсу з класів, а не з файлової системи, то можна використовувати префікс classpath:
з Spring.
Зверни увагу, що підтримка тестування для реалізацій WebApplicationContext
у Spring знаходиться
на одному рівні з підтримкою стандартних реалізацій ApplicationContext
. Під час тестування за
допомогою WebApplicationContext
можна вільно оголошувати конфігураційні XML-файли, скрипти Groovy
або класи, позначені анотацією @Configuration
, за допомогою анотації
@ContextConfiguration
. Ти також можеш вільно використовувати будь-які інші тестові анотації, такі
як @ActiveProfiles
, @TestExecutionListeners
, @Sql
, @Rollback
та інші.
Інші приклади в цьому розділі демонструють деякі з різних варіантів конфігурації для завантаження WebApplicationContext
.
У наступному прикладі показана підтримка фреймворком TestContext пріоритету угоди над конфігурацією:
@ExtendWith(SpringExtension.class)
// за замовчуванням "file:src/main/webapp
@WebAppConfiguration
// виявляє "WacTests-context.xml" у тому ж пакеті
// або статичні вкладені класи з анотацією
@Configuration
@ContextConfiguration
class WacTests {
//...
}
@ExtendWith(SpringExtension::class)
// за замовчуванням "file:src/main/webapp"
@WebAppConfiguration
// виявляє "WacTests-context.xml" у тому ж пакеті
// або статичні вкладені класи з анотацією
@Configuration
@ContextConfiguration class WacTests {
//...
}
Якщо ти позначаєш тестовий клас за допомогою анотації @WebAppConfiguration
без зазначення шляху до
бази ресурсів, шлях до ресурсів фактично набуває дефолтного значення file:src/main/webapp
.
Аналогічно, якщо ти оголосиш анотацію @ContextConfiguration
без зазначення
locations
ресурсів, компонентних classes
або initializers
контексту,
то Spring намагається виявити наявність вашої конфігурації, використовуючи угоди (тобто
WacTests-context.xml
у тому ж пакеті, що і клас WacTests
або статичні вкладені класи, анотовані @Configuration
).
У наступному прикладі показано, як явно оголошувати шлях до бази ресурсів за допомогою анотації @WebAppConfiguration
та розташування XML-ресурсів за допомогою анотації @ContextConfiguration
:
@ExtendWith(SpringExtension.class)
// ресурс файлової системи @WebAppConfiguration("webapp")
// ресурс classpath
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
//...
}
@ExtendWith(SpringExtension::class)
// ресурс файлової системи
@WebAppConfiguration("webapp")
// ресурс classpath
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
//...
}
Тут важливо відзначити різну семантику для шляхів при цих двох анотаціях. За замовчуванням шляхи до ресурсів,
анотовані @WebAppConfiguration
, засновані на файловій системі, в той час як розташування
ресурсів, анотованих @ContextConfiguration
, засновані на classpath.
У цьому прикладі показано, що ми можемо перевизначити семантику ресурсів за умовчанням для обох анотацій, вказавши префікс ресурсів Spring:
@ExtendWith(SpringExtension.class)
// ресурс classpath
@WebAppConfiguration("classpath:test-web-resources")
// ресурс файлової системи
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
//. ..
}
@ExtendWith(SpringExtension::class)
// ресурс classpath
@WebAppConfiguration("classpath:test-web-resources")
// ресурс файлової системи
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
//...
}
Порівняй коментарі в цьому прикладі з попереднім.
Щоб забезпечити всебічну підтримку веб-тестування, фреймворк TestContext має
ServletTestExecutionListener
,
який активовано за умовчанням. Під час тестування на WebApplicationContext
цей слухач TestExecutionListener
встановлює локальний стан потоку за умовчанням за допомогою RequestContextHolder
зі Spring Web
перед кожним тестовим методом і створює MockHttpServletRequest
,
MockHttpServletResponse
та ServletWebRequest
на основі шляху до основного ресурсу,
налаштованого за допомогою @WebAppConfiguration
. Слухач
ServletTestExecutionListener
також забезпечує, щоб MockHttpServletResponse
та
ServletWebRequest
могли бути впроваджені до тестового екземпляру, а після завершення тесту він
очищує локальний стан потоку.
Після завантаження WebApplicationContext
для твого
тесту можна виявити, що необхідно взаємодіяти з веб-імітаціями — наприклад, для налаштування тестового стенду
або для виконання тверджень після виклику вашого вебкомпонента. У наступному прикладі показано, які
об'єкти-імітації можна автоматично виявити та зв'язати з вашим екземпляром тесту. Зверни увагу, що WebApplicationContext
та MockServletContext
кешуються для всього тестового комплекту, тоді як інші об'єкти-імітації
керуються кожним тестовим методом за допомогою ServletTestExecutionListener
.
@SpringJUnitWebConfig
class WacTests {
@Autowired
WebApplicationContext wac; // кешується
@Autowired
MockServletContext servletContext; // кешується
@Autowired
MockHttpSession session;
@Autowired
MockHttpServletRequest request;
@Autowired
MockHttpServletResponse response;
@Autowired
ServletWebRequest webRequest;
//...
}
@SpringJUnitWebConfig
class WacTests {
@Autowired
lateinit var wac: WebApplicationContext // кешується
@Autowired
lateinit var servletContext: MockServletContext // кешується
@Autowired
lateinit var session: MockHttpSession
@Autowired
lateinit var request: MockHttpServletRequest
@Autowired
lateinit var response: MockHttpServletResponse
@Autowired
lateinit var webRequest: ServletWebRequest
//...
}
Кешування контексту
Щойно фреймворк TestContext завантажить ApplicationContext
(або WebApplicationContext
) для
тесту цей контекст кешується і повторно використовується для всіх наступних тестів, які
оголошують ту ж унікальну контекстну конфігурацію в рамках одного тестового комплекту. Щоб зрозуміти, як
працює кешування, важливо розуміти, що мається на увазі під "унікальним" і "тестовим комплектом". Отже,
унікальна комбінація параметрів конфігурації використовується для генерації ключа, під яким кешується
контекст. Фреймворк TestContext використовує наступні параметри конфігурації для побудови ключа контекстного
кешу:
locations
(з@ContextConfiguration
)classes
(з@ContextConfiguration
)contextInitializerClasses
(з@ContextConfiguration
)contextCustomizers
(зContextCustomizerFactory
) — сюди входять методи, позначені анотацією@DynamicPropertySource
, а також різні функції із засобів підтримки тестування Spring Boot, такі як анотації@MockBean
та@SpyBean
.contextLoader
(з@ContextConfiguration
)parent
(з@ContextHierarchy
)activeProfiles
(з@ActiveProfiles
)propertySourceLocations
(з@TestPropertySource
)propertySourceProperties
(з@TestPropertySource
)resourceBasePath
(з@WebAppConfiguration
)
Наприклад, якщо TestClassA
задає {"app-config.xml", "test-config.xml".}
для
атрибуту locations
(або value
), анотованого @ContextConfiguration
,
фреймворк TestContext завантажує відповідний ApplicationContext
та зберігає його у статичному
кеші контексту під ключем, який заснований виключно на цих місцях. Так, якщо TestClassB
також
визначає {"app-config.xml", "test-config.xml"}
для своїх місць (явно чи неявно через
успадкування), але не визначає анотацію @WebAppConfiguration
, інший завантажувач
ContextLoader
,
інші активні профілі, інші ініціалізатори контексту, інші джерела тестових властивостей або інший
батьківський контекст, то той самий ApplicationContext
використовується спільно обома
тестовими класами. Це означає, що ресурси на завантаження контексту програми витрачаються лише один раз
(для кожного тестового комплекту), а подальше виконання тестів відбувається набагато швидше.
статичній
змінній. Іншими словами, якщо тести виконуються в окремих процесах,
статичний кеш очищається між кожним виконанням тесту, що дозволяє ефективно дезактивувати механізм кешування.
Щоб скористатися перевагами механізму кешування, всі тести повинні виконуватися в рамках одного процесу або
тестового комплекту. Це можна досягти, виконуючи всі тести як групу в IDE. Так само при виконанні тестів за
допомогою такого фреймворку збірки, як Ant, Maven або Gradle, важливо переконатися, що фреймворк збірки не
розгалужується між тестами. Наприклад, якщо forkMode
для плагіна Maven Surefire встановлений у always
або pertest
, фреймворк TestContext не зможе кешувати контексти додатків між тестовими класами,
внаслідок чого процес складання буде виконуватися значно повільніше.Розмір контекстного кешу обмежений, максимальний розмір за замовчуванням становить 32. При досягненні
максимального розміру
використовується політика витіснення за давністю використання (LRU) для витіснення та закриття застарілих
контекстів. Можна налаштувати максимальний розмір командного рядка або скрипта збирання, встановивши системну
властивість JVM під назвою spring.test.context.cache.maxSize
. Як варіант, можна встановити цю ж
властивість через механізм SpringProperties
.
Оскільки велика кількість контекстів програми, завантажених у конкурентному тестовому комплекті, може призвести
до того, що комплект буде виконуватися занадто довго, часто корисно знати, скільки саме контекстів було
завантажено та кешовано. Щоб переглянути статистику базового контекстного кешу, можна встановити рівень ведення
журналу для категорії журналування org.springframework.test.context.cache
у значення
DEBUG
.
У малоймовірному випадку, якщо тест зашкодить контекст програми та потребуватиме перезавантаження (наприклад,
шляхом зміни визначення біна або стану об'єкта програми), можна анотувати свій тестовий клас або тестовий метод
за допомогою @DirtiesContext
(див. опис анотації @DirtiesContext
у розділі "Анотації
для тестування в Spring"). Таким чином Spring задається команда видалити контекст із кешу і
перебудувати контекст програми перед запуском наступного тесту, якому потрібен той самий контекст програми.
Зверніть увагу, що підтримка анотації @DirtiesContext
забезпечується слухачами DirtiesContextBeforeModesTestExecutionListener
та DirtiesContextTestExecutionListener
, які активовані за умовчанням
Якщо необхідно налагодити тест, виконаний за допомогою Spring TestContext Framework, може бути корисно
проаналізувати консольний висновок (тобто висновок потоки SYSOUT
і SYSERR
). Деякі
вбудовані інструментальні засоби збирання та IDE можуть пов'язувати консольний висновок з певним тестом;
однак
легко зв'язати деякі консольні висновки з певним тестом не вийде. ApplicationContext
, який був
завантажений Spring TestContext Framework у тестовому комплекті.
ApplicationContext
для тесту зазвичай завантажується при підготовці екземпляра тестового класу —
наприклад, для виконання впровадження залежностей у поля тестового екземпляра, позначені анотацією
@Autowired
.
Це означає, що будь-який висновок повідомлень на консоль, запущений під час ініціалізації
ApplicationContext
,
зазвичай не можна пов'язати з окремим методом тестування. Однак, якщо контекст закривається безпосередньо
перед
виконанням тестового методу відповідно до семантики анотації
@DirtiesContext
, новий екземпляр контексту буде завантажений безпосередньо перед виконанням
тестового методу. В останньому сценарії IDE або вбудований інструментальний засіб збирання може потенційно
пов'язувати виведення повідомлень в консолі з окремим тестовим методом.
Контекст закривається відповідно до семантики анотації
@DirtiesContext
.Контекст закривається, тому що був автоматично видалений з кешу відповідно до політики витіснення LRU.
Контекст закривається через перехоплювач завершення JVM, коли робота JVM для тестового комплекту припиняється.
Якщо контекст закривається відповідно до семантики анотації @DirtiesContext
після певного
тестового
методу, IDE або вбудований інструментальний засіб складання потенційно може пов'язати виведення повідомлень
на
консоль з окремим тестовим методом. Якщо контекст закривається відповідно до семантики
@DirtiesContext
після виконання тестового класу, будь-яке виведення повідомлень в консолі, що
спрацьовує під час закриття ApplicationContext
, не можна зв'язати з окремим тестовим методом.
Крім
того, будь-який висновок повідомлень в консолі, що спрацьовує під час фази завершення через перехоплювач
завершення з JVM, не можна пов'язати з окремим тестовим методом. JVM, зворотні виклики, які виконуються під
час
фази завершення, виконуються в потоці з ім'ям SpringContextShutdownHook
. Таким чином, якщо
потрібно
дезактивувати виведення повідомлень на консоль, що спрацьовує при закритті ApplicationContext
через
перехоплювач завершення роботи з JVM, можна зареєструвати фільтр користувача в платформі журналування, яка
дозволить ігнорувати будь-яке журналування, ініційоване цим потоком.
Ієрархії контекстів
При написанні інтеграційних тестів, які використовують навантажений ApplicationContext
з Spring,
часто буває достатньо провести тестування щодо одного контексту. Однак трапляються випадки, коли корисно або
навіть необхідно проводити тестування щодо ієрархії екземплярів ApplicationContext
. Наприклад, якщо
ти розробляєш вебдодаток у Spring MVC, то у тебе зазвичай є кореневий WebApplicationContext
, що
завантажується ContextLoaderListener
зі Spring, а також дочірній WebApplicationContext
,
що
завантажується DispatcherServlet
зі Spring. Це призводить до створення ієрархії контекстів
"батько-дитина", де загальні компоненти та конфігурація інфраструктури оголошуються в кореневому контексті та
споживаються в дочірньому контексті компонентами, специфічними для інтернету. Інший приклад використання
зустрічається в додатках Spring Batch, де найчастіше існує батьківський контекст, який забезпечує конфігурацію
загальної пакетної інфраструктури, і дочірній контекст для конфігурації конкретного пакетного завдання.
Можна писати інтеграційні тести, що використовують ієрархії контекстів, оголошуючи конфігурацію контекста за
допомогою анотації @ContextHierarchy
або для окремого тестового класу, або всередині ієрархії
тестових класів. Якщо ієрархія контекстів оголошена для кількох класів в ієрархії тестових класів, можна також
об'єднати або перевизначити конфігурацію контексту для певного іменованого рівня в ієрархії контекстів. При
об'єднанні конфігурації для даного рівня ієрархії тип конфігураційних ресурсів (тобто конфігураційні файли XML
або компонентні класи) повинен бути узгодженим. В іншому випадку, цілком припустимо мати різні рівні в ієрархії
контекстів, налаштовані з використанням різних типів ресурсів. контекстів.
ControllerIntegrationTests
представляє типовий сценарій інтеграційного тестування для вебдодатку в
Spring MVC, оголошуючи ієрархію контекстів, що складається із двох рівнів; один для кореневого WebApplicationContext
(завантаженого за допомогою класу TestAppConfig @Configuration), а інший — для сервлета-диспетчера
WebApplicationContext (завантаженого за допомогою класу WebConfig @Configuration). WebApplicationContext, який
автоматично виявляється і зв'язується з екземпляром тесту, є контекстом для дочірнього контексту (тобто
найнижчим контекстом в ієрархії). У наступному списку показаний цей сценарій конфігурації:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {
@Autowired
WebApplicationContext wac;
// ...
}
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextHierarchy(
ContextConfiguration(classes = [TestAppConfig::class]),
ContextConfiguration(classes = [WebConfig::class]))
class ControllerIntegrationTests {
@Autowired
lateinit var wac: WebApplicationContext
// ...
}
Тестові класи в цьому прикладі визначають ієрархію контекстів усередині ієрархії тестових класів. AbstractWebTests
оголошує конфігурацію для кореневого WebApplicationContext
у вебзастосунку на базі Spring.
Зверни увагу, однак, що AbstractWebTests
не повідомляє анотацію @ContextHierarchy
.
Отже, підкласи AbstractWebTests
можуть опціонально брати участь в ієрархії контекстів або слідувати
стандартній для анотації @ContextConfiguration
семантиці. SoapWebServiceTests
та
RestWebServiceTests
розширюють AbstractWebTests
та визначають ієрархію контекстів за
допомогою анотації @ContextHierarchy
. В результаті завантажуються три контексти програми (по одному
для кожного оголошення анотації @ContextConfiguration
), а контекст програми, що завантажується на
основі конфігурації в AbstractWebTests
, встановлюється як батьківський контекст для кожного з
контексти, завантажені для конкретних підкласів. У наступному списку показаний цей сценарій конфігурації:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
abstract class AbstractWebTests
@ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml"))
class SoapWebServiceTests : AbstractWebTests()
@ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml"))
class RestWebServiceTests : AbstractWebTests()
Класи в цьому прикладі показують використання іменованих рівнів ієрархії для об'єднання конфігурації для певних
рівнів в ієрархії контекстів. BaseTests
визначає два рівні в ієрархії, а саме parent
та child
. ExtendedTests
розширює BaseTests
і дає Spring TestContext
Framework команду об'єднувати конфігурацію контексту для child
рівня ієрархії, забезпечуючи, щоб
імена, оголошені в атрибуті name
в анотації @ContextConfiguration
були
child
. В результаті завантажуються три контексти програми: один для /app-config.xml
,
один для /user-config.xml
, і один для {"/user- config.xml", "/order-config.xml"}
. Як і
в попередньому прикладі, контекст програми, завантажений з /app-config.xml
, встановлюється як
батьківський контекст для контекстів, завантажених із /user-config.xml
та {"/user-config.xml",
"/order-config.xml"}
. У наступному списку показаний цей сценарій конфігурації:
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}
@ContextHierarchy(
ContextConfiguration(name = "child", locations = ["/order-config.xml"])
)
class ExtendedTests : BaseTests() {}
На відміну від попереднього прикладу, цей приклад демонструє, як перевизначити конфігурацію для цього іменованого
рівня в ієрархії контекстів, встановивши прапор inheritLocations
в анотації
@ContextConfiguration
у false
. Отже, контекст програми ExtendedTests
завантажується тільки з /test-user-config.xml
,
а його батьком є контекст, завантажений з /app-config.xml
. У наступному списку показаний цей
сценарій конфігурації:
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(
name = "child",
locations = "/test-user-config.xml",
inheritLocations = false
))
class ExtendedTests extends BaseTests {}
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}
@ContextHierarchy(
ContextConfiguration(
name = "child",
locations = ["/test-user-config.xml"],
inheritLocations = false
))
class ExtendedTests : BaseTests() {}
@DirtiesContext
у тесті,
контекст якого налаштовано як частину ієрархії контекстів, можна використовувати прапор
hierarchyMode
для керування очищенням контексного кеша. За більш детальною інформацією звернися до
опису анотації @DirtiesContext
у розділі "Анотації
для тестування в Spring" та javadoc по інструкції @DirtiesContext
.