Каждый 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 {
// тело класса...
}
- Specifying the location of Groovy configuration files.
Если вы опустите атрибуты 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 будет загружен из the
// статический вложенный класс 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
в аннотации @ContextConfiguratio.n
установлен в 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 {
// тело класса...
}
Наследование и переопределение источников тестовых свойств
Аннотация @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
) для теста, этот контекст кэшируется и повторно используется для всех последующих тестов, которые объявляют ту же уникальную контекстную конфигурацию в рамках одного тестового комплекта. Чтобы понять, как работает кэширование, важно понимать, что подразумевается под "уникальным" и "тестовым комплектом".
ApplicationContext
можно однозначно идентифицировать по комбинации конфигурационных параметров, которая используется для его загрузки. Следовательно, уникальная комбинация конфигурационных параметров используется для генерации ключа, под которым кэшируется контекст. Фреймворк 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
используется совместно обоими тестовыми классами. Это означает, что ресурсы на загрузку контекста приложения затрачиваются только один раз (для каждого тестового комплекта), а последующее выполнение тестов происходит гораздо быстрее.
Фреймворк 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
, которые активированы по умолчанию.
Если необходимо отладить тест, выполненный с помощью Spring TestContext Framework, может быть полезно проанализировать консольный вывод (то есть вывод в потоки SYSOUT
и SYSERR
). Некоторые встроенные инструментальные средства сборки и IDE могут связывать консольный вывод с определенным тестом; однако легко связать некоторые консольные выводы с определенным тестом не выйдет.
Что касается вывода сообщений на консоль, запускаемого самим Spring Framework или компонентами, зарегистрированными в ApplicationContext
, важно понимать жизненный цикл ApplicationContext
, который был загружен Spring TestContext Framework в тестовом комплекте.
ApplicationContext
для теста обычно загружается при подготовке экземпляра тестового класса – например, для выполнения внедрения зависимостей в поля тестового экземпляра, помеченные аннотацией @Autowired
. Это означает, что любой вывод сообщений на консоль, запущенный во время инициализации ApplicationContext
, обычно нельзя связать с отдельным тестовым методом. Однако, если контекст закрывается непосредственно перед выполнением тестового метода в соответствии с семантикой аннотации @DirtiesContext
, новый экземпляр контекста будет загружен непосредственно перед выполнением тестового метода. В последнем сценарии IDE или встроенное инструментальное средство сборки может потенциально связывать вывод сообщений на консоль с отдельным тестовым методом.
ApplicationContext
для теста может быть закрыт по одному из указанных далее сценариев.
-
Контекст закрывается в соответствии с семантикой аннотации
@DirtiesContext
. -
Контекст закрывается, потому что был автоматически удален из кэша в соответствии с политикой вытеснения LRU.
-
Контекст закрывается через перехватчик завершения JVM, когда работа JVM для тестового комплекта прекращается.
Если контекст закрывается в соответствии с семантикой аннотации @DirtiesContext
после определенного тестового метода, IDE или встроенное инструментальное средство сборки потенциально может связать вывод сообщений на консоль с отдельным тестовым методом. Если контекст закрывается в соответствии с семантикой @DirtiesContext
после выполнения тестового класса, любой вывод сообщений на консоль, срабатывающий во время закрытия ApplicationContext
, нельзя связать с отдельным тестовым методом. Кроме того, любой вывод сообщений на консоль, срабатывающий во время фазы завершения через перехватчик завершения из JVM, нельзя связать с отдельным тестовым методом.
Если ApplicationContext
из Spring закрывается через перехватчик выключения из JVM, обратные вызовы, выполняемые во время фазы завершения, выполняются в потоке с именем SpringContextShutdownHook
. Таким образом, если нужно дезактивировать вывод сообщений на консоль, срабатывающий при закрытии ApplicationContext
через перехватчик завершения работы из JVM, можно зарегистрировать пользовательский фильтр в платформе журналирования, которая позволит игнорировать любое журналирование, инициированное этим потоком.
Иерархии контекстов
При написании интеграционных тестов, которые используют нагруженный ApplicationContext
из Spring, часто бывает достаточно провести тестирование относительно одного контекста. Однако бывают случаи, когда полезно или даже необходимо проводить тестирование относительно иерархии экземпляров ApplicationContext
. Например, если вы разрабатываете веб-приложение в Spring MVC, то у вас обычно есть корневой WebApplicationContext
, загружаемый ContextLoaderListener
из Spring, а также дочерний WebApplicationContext
, загружаемый DispatcherServlet
из Spring. Это приводит к созданию иерархии контекстов "родитель-ребенок", где общие компоненты и конфигурация инфраструктуры объявляются в корневом контексте и потребляются в дочернем контексте компонентами, специфичными для веб. Другой пример использования встречается в приложениях Spring Batch, где зачастую существует родительский контекст, который обеспечивает конфигурацию общей пакетной инфраструктуры, и дочерний контекст для конфигурации конкретного пакетного задания.
Можно писать интеграционные тесты, использующие иерархии контекстов, объявляя конфигурацию контекста с помощью аннотации @ContextHierarchy
либо для отдельного тестового класса, либо внутри иерархии тестовых классов. Если иерархия контекстов объявлена для нескольких классов в иерархии тестовых классов, также можно объединить или переопределить конфигурацию контекста для определенного именованного уровня в иерархии контекстов. При объединении конфигурации для данного уровня иерархии тип конфигурационных ресурсов (то есть конфигурационные XML-файлы или компонентные классы) должен быть согласованным. В противном случае, вполне допустимо иметь разные уровни в иерархии контекстов, сконфигурированные с использованием разных типов ресурсов.
В остальных основывающихся на JUnit Jupiter примерах из этого раздела наглядно видно общие сценарии конфигурации для интеграционных тестов, которые требуют использования иерархий контекстов.
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
.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ