Кожен TestContext забезпечує керування контекстом та підтримку кешування для екземпляра тесту, за який він відповідає. Тестові екземпляри не отримують доступ до налаштованого ApplicationContext автоматично. Проте, якщо тестовий клас реалізує інтерфейс ApplicationContextAware, посилання на ApplicationContext передається тестовому екземпляру. Зверни увагу, що AbstractJUnit4SpringContextTests та AbstractTestNGSpringContextTests реалізують ApplicationContextAware і, отже, забезпечують доступ до ApplicationContext автоматично.

ApplicationContext, позначений анотацією @Autowired

В якості альтернативи реалізації інтерфейсу ApplicationContextAware можна впровадити контекст програми для твого тестового класу через анотацію @Autowired для поля або сетера, як показано в наступному прикладі:

Java

@SpringJUnitConfig
class MyTest {
    @Autowired 
    ApplicationContext applicationContext;
    // тіло класу...
}
  1. Впроваджуємо ApplicationContext.
Kotlin

@SpringJUnitConfig
class MyTest {
    @Autowired 
    lateinit var applicationContext: ApplicationContext
    // тіло класу...
}
  1. Впроваджуємо ApplicationContext.

Аналогічно, якщо тест налаштований на завантаження WebApplicationContext, то можна впровадити контекст вебпрограми до тесту таким чином:

Java

@SpringJUnitWebConfig 
class MyWebAppTest {
    @Autowired 
    WebApplicationContext wac;
    // тіло класу...
}
  1. Конфігуруємо WebApplicationContext.
  2. Впроваджуємо WebApplicationContext.
Kotlin

@SpringJUnitWebConfig 
class MyWebAppTest {
    @Autowired 
    lateinit var wac: WebApplicationContext
    // тіло класу...
}
  1. Конфігуруємо WebApplicationContext.
  2. Впроваджуємо 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: і т.д.), використовується як є.

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з "/app-config.xml" та
// "/test-config.xml" в корені шляху класів
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) 
class MyTest {
    // тіло класу...
}
  1. Встановлюємо атрибут розташування у списку XML-файлів.
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з " /app-config.xml" та
// "/test-config.xml" в корені шляху класів
@ContextConfiguration("/app-config.xml", "/test-config.xml") 
class MyTest {
    // тіло класу...
}
  1. Встановлюємо атрибут розташування у списку XML-файлів.

Анотація @ContextConfiguration підтримує псевдонім для атрибуту locations через стандартний атрибут value з Java. Таким чином, якщо не потрібно оголошувати додаткові атрибути в анотації @ContextConfiguration, то можна опустити оголошення імені атрибута locations і оголосити розташування ресурсів, використовуючи скорочений формат, продемонстрований у наступному прикладі:

Java

@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) 
class MyTest {
    // тіло класу...
}
  1. Встановлюємо XML-файли без використання атрибуту location.
Kotlin
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-config.xml") 
class MyTest {
    // тіло класу...
}
  1. Встановлюємо XML-файли без використання атрибуту location.

Якщо ти опустиш атрибути location та value в анотації @ContextConfiguration, фреймворк TestContext спробує визначити розташування XML-ресурсу за замовчуванням. Зокрема, GenericXmlContextLoader та GenericXmlWebContextLoader визначають місцезнаходження за замовчуванням на основі імені тестового класу. Якщо твій клас називається com.example.MyTest, GenericXmlContextLoader завантажує контекст програми з "classpath:com/example/MyTest-context.xml". У цьому прикладі показано, як це зробити:

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з
// "classpath:com/example/ MyTest-context.xml"
@ContextConfiguration 
class MyTest {
    // тіло класу...
}
  1. Завантажуємо конфігурацію з розташування за замовчуванням.
Kotlin
@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration 
class MyTest {
    // тіло класу...
}
  1. Завантажуємо конфігурацію з розташування за замовчуванням.
Конфігурація контексту за допомогою сценаріїв Groovy

Щоб завантажити ApplicationContext для тестів за допомогою скриптів Groovy, які використовують Groovy Bean Definition DSL.
Також ти можеш анотувати свій тестовий клас за допомогою @ContextConfiguration і налаштувати атрибут locations або value за допомогою масиву, що містить розташування ресурсів скриптів Groovy. Семантика пошуку ресурсів для скриптів Groovy така сама, як і для конфігураційних XML-файлів.

Активація підтримки скриптів Groovy
Підтримка використання скриптів Groovy для завантаження ApplicationContext у Spring TestContext Framework активується автоматично, якщо Groovy знаходиться в classpath.

У наступному прикладі показано, як задати конфігураційні файли Groovy:

Java
@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з "/AppConfig.groovy" та
// "/TestConfig.groovy" в корені шляху класів
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) 
class MyTest {
    // тіло класу...
}
Kotlin

@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з "/AppConfig.groovy" і
// "/TestConfig.groovy" в корені шляху класів
@ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy" ) 
class MyTest {
    // тіло класу...
}
  1. Визначення місцезнаходження файлів Groovy.

Якщо ти опустиш атрибути locations і value з анотації @ContextConfiguration, фреймворк TestContext спробує визначити скрипт Groovy за замовчуванням. Зокрема, GenericGroovyXmlContextLoader та GenericGroovyXmlWebContextLoader визначають місцезнаходження за замовчуванням на основі імені тестового класу. Якщо твій клас має ім'я com.example.MyTest, завантажувач контексту Groovy завантажить контекст вашої програми з "classpath:com/example/MyTestContext.groovy". У цьому прикладі показано, як використовувати значення за замовчуванням:

Java

@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з
// "classpath:com/ example/MyTestContext.groovy"
@ContextConfiguration 
class MyTest {
    // тіло класу...
}
  1. Завантажуємо конфігурацію з розташування за замовчуванням.
Kotlin

@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration 
class MyTest {
    // тіло класу...
}
  1. Завантажуємо конфігурацію з розташування за замовчуванням.
Одночасне оголошення XML-конфігурації та скриптів Groovy

Ти можеш оголосити конфігураційні XML-файли та скрипти Groovy одночасно, використовуючи атрибут location або value в анотації @ContextConfiguration. Якщо шлях до розташування конфігурованого ресурсу закінчується .xml, він завантажується за допомогою XmlBeanDefinitionReader. В іншому випадку він завантажується за допомогою GroovyBeanDefinitionReader.

У наступному лістингу показано, як об'єднати обидва варіанти в інтеграційному тесті:

Java

@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з
// "/app-config.xml" та "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/ TestConfig.groovy" })
class MyTest {
    // тіло класу...
}
Kotlin

@ExtendWith (SpringExtension::class)
// ApplicationContext буде завантажено з
// "/app-config.xml" та "/TestConfig.groovy"
@ContextConfiguration("/app-config.xml", "/TestConfig.groovy")
class MyTest {
    // тіло класу...
}
Конфігурація контексту з використанням компонентних класів

Щоб завантажити ApplicationContext для тестів за допомогою компонентних класів (див. розділ "Конфігурація контейнера на базі Java"), можна анотувати свій тестовий клас за допомогою @ContextConfiguration та налаштувати атрибут classes за допомогою масиву, що містить посилання на компонентні класи. У цьому прикладі показано, як це зробити:

Java

@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з AppConfig та TestConfig
@ContextConfiguration(classes = { AppConfig.class, TestConfig.class}) 
class MyTest {
    // тіло класу...
}
  1. Зазначення компонентних класів.
Kotlin

@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з AppConfig та TestConfig
@ContextConfiguration(classes = [AppConfig::class, TestConfig::class])  
class MyTest {
    // тіло класу...
}
  1. Зазначення компонентних класів.
Компонентні класи

Термін "компонентний клас" може стосуватися будь-якого з наступних:

  • Клас, анотований за допомогою @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 для тестового класу:

Java

@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
    }
}
  1. Завантаження інформації про конфігурацію з вкладеного класу Config.
Kotlin

@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
    }
}
  1. Завантаження інформації про конфігурацію з вкладеного класу 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. У цьому прикладі показано, як використовувати ініціалізатори:

Java

@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з TestConfig
// та ініціалізований за допомогою TestAppCtxInitializer
@ContextConfiguration(
    classes = TestConfig.class,
    initializers = TestAppCtxInitializer.class) 
class MyTest {
    // тіло класу...
} 
  1. Завдання конфігурації за допомогою конфігураційного класу та ініціалізатора.
Kotlin

@ExtendWith(SpringExtension::class)
// ApplicationContext буде завантажений з TestConfig
// та ініціалізований за допомогою TestAppCtxInitializer
@ContextConfiguration(
    classes = [TestConfig::class],
    initializers = [TestAppCtxInitializer::class ]) 
class MyTest {
    // тіло класу...
}
  1. Встановлення конфігурації за допомогою конфігураційного класу та ініціалізатора.

Також можна повністю опустити оголошення конфігураційних XML-файлів, скриптів Groovy або компонентних класів в анотації @ContextConfiguration і натомість оголосити тільки класи ApplicationContextInitializer, які потім відповідатимуть за реєстрацію бінів у контексті – наприклад, шляхом програмного завантаження визначень бінів із XML-файлів чи конфігураційних класів. У цьому прикладі показано, як це зробити:

Java

@ExtendWith(SpringExtension.class)
// ApplicationContext буде ініціалізований за допомогою EntireAppInitializer
// який, як передбачається, реєструє біни в контексті
@ContextConfiguration(initializers = EntireAppInitializer.class) 
class MyTest {
    // тіло класу...
}
  1. Встановлення конфігурації за допомогою лише ініціалізатора.
Kotlin

@ExtendWith(SpringExtension::class)
// ApplicationContext буде ініціалізований за допомогою EntireAppInitializer
// який, як передбачається, реєструє біни в контексті
@ContextConfiguration(initializers = [EntireAppInitializer::class]) 
class MyTest {
    // тіло класу...
}
  1. Встановлення конфігурації за допомогою лише ініціалізатора.
Спадкування конфігурації контексту

Анотація @ContextConfiguration підтримує булеви атрибути inheritLocations та inheritInitializers, які позначають, чи слід успадковувати розташування ресурсів або компонентні класи та ініціалізатори контексту, оголошені суперкласами. За замовчуванням значення для обох прапорів є true. Це означає, що тестовий клас успадковує розташування ресурсів або компонентні класи, а також ініціалізатори контексту, оголошені будь-якими суперкласами. Зокрема, розташування ресурсів або компонентні класи для тестового класу додаються до списку розташування ресурсів або анотованих класів, оголошених суперкласами. Так само ініціалізатори для даного тестового класу додаються до набору ініціалізаторів, визначених суперкласами тестів. Таким чином, підкласи можуть розширювати розташування ресурсів, компонентні класи або ініціалізатори контексту.

Якщо атрибут inheritLocations або inheritInitializers в анотації @ContextConfiguration встановлений у false, то розташування ресурсів або компонентні класи та ініціалізатори контексту для тестового класу, відповідно, "затіняють" і ефективно замінюють конфігурацію, визначену суперкласами.

Починаючи зі Spring Framework 5.3, конфігурація тестів може також успадковуватися від об'ємних класів. Див. подробиці у розділі "Конфігурація тестового класу з анотацією @Nested".

У наступному прикладі, в якому використовуються розташування XML-ресурсів, ApplicationContext для ExtendedTest завантажується з base-config.xml та extended-config.xml саме в такому порядку. Тому біни, визначені в extended-config.xml, можуть перевизначати (тобто замінювати) біни, визначені у base-config.xml. У наступному прикладі показано, як один клас може розширювати інший і використовувати як власний конфігураційний файл, так і конфігураційний файл суперкласу:

Java

@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 {
    // тіло класу...
}
  1. Конфігураційний файл, визначений у суперкласі.
  2. Конфігураційний файл, визначений у підкласі.
Kotlin

@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 () {
    // тіло класу...
}
  1. Конфігураційний файл, визначений у суперкласі.
  2. Конфігураційний файл, визначений у підкласі.

Точно так само в наступному прикладі, в якому використовуються компонентні класи, ApplicationContext для ExtendedTest завантажується з класів BaseConfig та ExtendedConfig саме в такому порядку. Тому біни, визначені в ExtendedConfig, можуть перевизначати (тобто замінювати) біни, визначені у BaseConfig. В наступному прикладі показано, як один клас може розширювати інший і використовувати як свій власний конфігураційний клас, так і клас класу конфігурації:

Java

// ApplicationContext буде завантажений з BaseConfig
@SpringJUnitConfig(BaseConfig.class) 
class BaseTest {
    // тіло класу...
}
// ApplicationContext буде завантажений з BaseConfig і ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class) 
class ExtendedTest extends BaseTest {
    // тіло класу...
}
  1. Конфігураційний клас, визначений у суперкласі.
  2. Конфігураційний клас, визначений у підкласі.
Kotlin

// ApplicationContext буде завантажений з BaseConfig
@SpringJUnitConfig(BaseConfig::class) 
open class BaseTest {
    // тіло класу...
}
// ApplicationContext буде завантажено з BaseConfig та ExtendedConfig
@SpringJUnitConfig(ExtendedConfig::class) 
class ExtendedTest : BaseTest() {
    // тіло класу...
}
  1. Конфігураційний клас, визначений у суперкласі.
  2. Конфігураційний клас, визначений у підкласі.

У наступному прикладі, в якому використовуються ініціалізатори контексту, ApplicationContext для ExtendedTest ініціалізується за допомогою BaseInitializer та ExtendedInitializer. Зверни увагу, однак, що порядок виклику ініціалізаторів залежить від того, чи реалізують вони інтерфейс Ordered зі Spring або чи позначені вони анотацією @Order з Spring або стандартною анотацією @Priority. У наступному прикладі показано, як один клас може розширювати інший і використовувати як свій власний ініціалізатор, так і ініціалізатор суперкласу:

Java

// ApplicationContext буде ініціалізований BaseInitializer
@SpringJUnitConfig (initializers = BaseInitializer.class) 
class BaseTest {
    // тіло класу...
}
// ApplicationContext буде ініціалізований BaseInitializer
// та ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class) 
class ExtendedTest extends BaseTest {
    // тіло класу...
}
  1. Ініціалізатор, визначений у суперкласі.
  2. Ініціалізатор, певний в підкласі.

// ApplicationContext буде ініціалізований BaseInitializer
@SpringJUnitConfig(initializers = [BaseInitializer::class]) 
open class BaseTest {
    // тіло класу...
}
// ApplicationContext буде ініціалізований BaseInitializer
// та ExtendedInitializer
@SpringJUnitConfig(initializers = [ExtendedInitializer::class]) 
class ExtendedTest : BaseTest() {
    // тіло класу...
}
  1. Ініціалізатор, визначений у суперкласі.
  2. Ініціалізатор, визначений у підкласі.
Конфігурація контексту з використанням профілів оточення

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>
Java

@ExtendWith(SpringExtension.class)
// ApplicationContext буде завантажений з " classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
    @Autowired
    TransferService transferService;
    @Test
    void testTransferService() {
        // тестуємо transferService
    }
}
Kotlin

@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:

Java

@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();
    }
}
Kotlin

@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()
    }
}
Java

@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");
    }
}
Kotlin

@Configuration
@Profile("production")
class JndiDataConfig {
    @Bean(destroyMethod = "")
    fun dataSource(): DataSource {
        val ctx = InitialContext()
        return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
    }
}
Java

@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();
    }
}
Kotlin

@Configuration
@Profile("default")
class DefaultDataConfig {
    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .build()
    }
}
Java

@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();
    }
}
Kotlin

@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()
    }
}
Java

@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {
    @Autowired
    TransferService transferService;
    @Test
    void testTransferService() {
        // тестуємо transferService
    }
}
Kotlin

@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:

Починаючи зі Spring Framework 5.3, тестова конфігурація може також успадковуватися від об'ємних класів. Див. подробиці у розділі "Конфігурація тестового класу з анотацією @Nested".
Java

@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
Kotlin

@SpringJUnitConfig(
        TransferServiceConfig::class,
        StandaloneDataConfig::class,
        JndiDataConfig::class,
        DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
Java

// профіль "dev" успадковується від суперкласу
class TransferServiceTest extends AbstractIntegrationTest {
    @Autowired
    TransferService transferService;
    @Test
    void testTransferService() {
        // тестуємо transferService
    }
}
Kotlin

// профіль "dev" успадковується від суперкласу
class TransferServiceTest : AbstractIntegrationTest() {
    @Autowired
    lateinit var transferService: TransferService
    @Test
    fun testTransferService() {
        // тестуємо transferService
    }
}

Анотація @ActiveProfiles також підтримує атрибут inheritProfiles, який можна використовувати для дезактивації успадкування активних профілів, як показано в наступному прикладі:

Java

// профіль "dev" перевизначається на "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
    // тіло тіста
}
Kotlin

// профіль "dev" перевизначається на "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
    // тіло тесту
}

Більш того, іноді буває необхідно дозволити активні профілі для тестів програмно, а не декларативно — наприклад, виходячи з:

  • Поточної операційної системи.

  • Факта виконання тестів на сервері збірки для забезпечення безперервної інтеграції

  • Наявності певних змінних оточення.

  • Наявності власних анотацій на рівні класів.

  • Іншої наскрізної функціональності.

Щоб програмно дозволяти профілі визначення активних бінів, можна реалізувати користувальницький ActiveProfilesResolver та зареєструвати його за допомогою атрибуту resolver в анотації @ActiveProfiles. Для отримання додаткових відомостей див. javadoc. У наступному прикладі продемонстровано, як реалізувати та зареєструвати користувача OperatingSystemActiveProfilesResolver:

Java

// профіль "dev" перевизначається програмно за допомогою розпізнавача користувача
@ActiveProfiles(
       resolver = OperatingSystemActiveProfilesResolver.class,
       inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
    // тіло тесту
}
Kotlin

// профіль "dev" перевизначається програмно за допомогою розпізнавача користувача
@ActiveProfiles(
       resolver = OperatingSystemActiveProfilesResolver::class,
       inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
    // тіло тіста
}
Java

public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
    @Override
    public String[] resolve(Class<?> testClass) {
        String profile = ...;
        // Визначаємо значення профілю, виходячи з операційної системи
        return new String[] {profile};
    }
}
Kotlin

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.

У цьому прикладі використовується файл тестових властивостей:

Java
@ContextConfiguration
@TestPropertySource("/test.properties") 
class MyIntegrationTests {
    // тіло класу...
}
  1. Вказуємо файл властивостей з абсолютним шляхом.
Kotlin

@ContextConfiguration
@TestPropertySource("/test.properties" ) 
class MyIntegrationTests {
    // тіло класу...
}
  1. Вказуємо файл властивостей з абсолютним шляхом.

Можна конфігурувати вбудовані властивості у вигляді пар ключ-значення, використовуючи атрибут properties в анотації @TestPropertySource, як показано в наведеному нижче прикладі. Всі пари ключ-значення додаються в об'ємну Environment як один тестовий PropertySource з найвищим рівнем старшинства.

Підтримуваний синтаксис для пар ключ-значення такий самий, як та синтаксис, визначений для записів у файлі властивостей Java:

  • key=value

  • key:value

  • key value

У наступному прикладі зазначені дві властивості, що вбудовуються:

Java
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) 
class MyIntegrationTests {
    // тіло класу...
}
  1. Зазначення двох властивостей за допомогою двох варіантів синтаксису "ключ-значення".
Kotlin
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) 
class MyIntegrationTests {
    // тіло класу...
}
  1. Зазначення двох властивостей за допомогою двох варіантів синтаксису "ключ-значення".

Починаючи з версії 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. У наступному прикладі показано, як задати властивості як у файлі, так і вбудовано:

Java

@ContextConfiguration
@TestPropertySource(
    locations = "/test.properties",
    properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
    // тіло класу...
}
Kotlin

@ContextConfiguration
@TestPropertySource("/test.properties",
        properties = ["timezone = GMT", "port: 4242"]
)
class MyIntegrationTests {
    // class body...
}
Спадкування та перевизначення джерел тестових властивостей

Анотація @TestPropertySource підтримує булеві атрибутиinheritLocations та inheritProperties, які позначають, чи слід успадковувати розташування ресурсів для файлів властивостей і вбудованих властивостей, оголошених суперкласами. За замовчуванням для обох прапорів є true. Це означає, що тестовий клас успадковує розташування та вбудовувані властивості, оголошені будь-якими суперкласами. Зокрема, розташування та вбудовувані властивості для тестового класу додаються до місць розташування та вбудовуваних властивостей, оголошених суперкласами. Таким чином, підкласи можуть розширювати розташування та вбудовувані властивості. Зауважте, що властивості, які з'являються пізніше, "затіняють" (тобто перевизначають) однойменні властивості, які з'являються раніше. До того ж, згадані вище правила старшинства застосовуються і для успадкованих джерел тестових властивостей.

Якщо атрибут inheritLocations або inheritPropertie.s в анотації @TestPropertySource встановлений у false, то розташування або вбудовувані властивості для тестового класу, відповідно, "затіняють" і ефективно замінюють конфігурацію, визначену суперкласами.

Починаючи зі Spring Framework 5.3, тестова конфігурація може також успадковуватися від об'ємних класів. Див. подробиці у розділі "Конфігурація тестового класу з анотацією @Nested".

У наступному прикладі ApplicationContext для BaseTest завантажується лише за допомогою файлу base.properties як джерела тестових властивостей. В той же час ApplicationContext для ExtendedTest завантажується з використанням файлів base.properties та extended.properties як джерела тестових властивостей. У наступному прикладі показано, як визначити властивості як у підкласі, так і в його суперкласі за допомогою файлів properties:

Java

@TestPropertySource ("base.properties")
@ContextConfiguration
class BaseTest {
    // ...
}
@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}
Kotlin

@TestPropertySource("base.properties")
@ContextConfiguration
open class BaseTest {
    // ...
}
@TestPropertySource("extended.properties" )
@ContextConfiguration
class ExtendedTest : BaseTest() {
    // ...
}

У наступному прикладі ApplicationContext для BaseTest завантажується тільки за допомогою лише вбудованої властивості key1. В той же час ApplicationContext для ExtendedTest завантажується за допомогою вбудованих властивостей key1 та key2. У наступному прикладі показано, як визначити властивості як у підкласі, так і в його суперкласі за допомогою вбудованих властивостей:

Java

@TestPropertySource(properties = "key1 = value1 ")
@ContextConfiguration
class BaseTest {
    // ...
}
@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}
Kotlin
@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 з коректними динамічними властивостями.

Java

@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);
    }
    // тести...
}
Kotlin

@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 пріоритету угоди над конфігурацією:

Java

@ExtendWith(SpringExtension.class)
// за замовчуванням "file:src/main/webapp
@WebAppConfiguration
// виявляє "WacTests-context.xml" у тому ж пакеті
// або статичні вкладені класи з анотацією
@Configuration
@ContextConfiguration
class WacTests {
    //...
}
Kotlin

@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:

Java

@ExtendWith(SpringExtension.class)
// ресурс файлової системи @WebAppConfiguration("webapp")
// ресурс classpath
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}
Kotlin

@ExtendWith(SpringExtension::class)
// ресурс файлової системи
@WebAppConfiguration("webapp")
// ресурс classpath
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}

Тут важливо відзначити різну семантику для шляхів при цих двох анотаціях. За замовчуванням шляхи до ресурсів, анотовані @WebAppConfiguration, засновані на файловій системі, в той час як розташування ресурсів, анотованих @ContextConfiguration, засновані на classpath.

У цьому прикладі показано, що ми можемо перевизначити семантику ресурсів за умовчанням для обох анотацій, вказавши префікс ресурсів Spring:

Java

@ExtendWith(SpringExtension.class)
// ресурс classpath
@WebAppConfiguration("classpath:test-web-resources")
// ресурс файлової системи
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
    //. ..
}
Kotlin

@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.

Java

@SpringJUnitWebConfig
class WacTests {
    @Autowired
    WebApplicationContext wac; // кешується
    @Autowired
    MockServletContext servletContext; // кешується
    @Autowired
    MockHttpSession session;
    @Autowired
    MockHttpServletRequest request;
    @Autowired
    MockHttpServletResponse response;
    @Autowired
    ServletWebRequest webRequest;
    //...
}
Kotlin

@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)

  • contextCustomizersContextCustomizerFactory) — сюди входять методи, позначені анотацією @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 використовується спільно обома тестовими класами. Це означає, що ресурси на завантаження контексту програми витрачаються лише один раз (для кожного тестового комплекту), а подальше виконання тестів відбувається набагато швидше.

Тестові комплекти та розгалужені процеси
Фреймворк TestContext зі Spring зберігає контексти додатків у статичному кеші. Це означає, що контекст буквально зберігається в статичній змінній. Іншими словами, якщо тести виконуються в окремих процесах, статичний кеш очищається між кожним виконанням тесту, що дозволяє ефективно дезактивувати механізм кешування. Щоб скористатися перевагами механізму кешування, всі тести повинні виконуватися в рамках одного процесу або тестового комплекту. Це можна досягти, виконуючи всі тести як групу в 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, які активовані за умовчанням

.
Життєвий цикл ApplicationContext і виведення повідомлень на консоль

Якщо необхідно налагодити тест, виконаний за допомогою 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, який автоматично виявляється і зв'язується з екземпляром тесту, є контекстом для дочірнього контексту (тобто найнижчим контекстом в ієрархії). У наступному списку показаний цей сценарій конфігурації:

Java
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {
    @Autowired
    WebApplicationContext wac;
    // ...
}
Kotlin
@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, встановлюється як батьківський контекст для кожного з контексти, завантажені для конкретних підкласів. У наступному списку показаний цей сценарій конфігурації:

Java
@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 {}
Kotlin
@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"}. У наступному списку показаний цей сценарій конфігурації:

Java
@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 {}
Kotlin

@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. У наступному списку показаний цей сценарій конфігурації:

Java
@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 {}
Kotlin
@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.