Учитывая код , найденный здесь , я могу программно запускать тесты огурцов.
Внутри класса определения шага я прокомментировал внедрение конструктора DummyService.
Если раскомментирую, то получу (что и ожидается)
o.cucumber.core.exception.CucumberException: class org.example.cucumberseviceinjection.StepDefinitions does not have a public zero-argument constructor.
To use dependency injection add an other ObjectFactory implementation such as:
* cucumber-picocontainer
* cucumber-spring
* cucumber-jakarta-cdi
Чтобы это исправить, я раскомментирую следующую зависимость pom.xml:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
</dependency>
Если я запущу приложение сейчас, я получу следующее исключение:
io.cucumber.core.backend.CucumberBackendException: Please annotate a glue class with some context configuration.
For example:
@CucumberContextConfiguration
@SpringBootTest(classes = TestConfig.class)
public class CucumberSpringConfiguration { }
Or:
@CucumberContextConfiguration
@ContextConfiguration( ... )
public class CucumberSpringConfiguration { }
Почему я получаю это, когда я определил внутри класса бегуна свойство клея .configurationParameter("cucumber.glue", "org.example.cucumberseviceinjection")?
Кроме того, я добавил недостающую аннотацию к классу определения шага. После этого шага я получаю:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.example.cucumberseviceinjection.StepDefinitions': Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'org.example.cucumberseviceinjection.DummyService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Основываясь на документации, я понимаю, что при запуске в качестве теста из-за аннотации класса конфигурации с помощью @SpringBootTest контекст Spring создается правильно, и мы можем внедрить другие сервисы в класс определения шага.
Как я могу получить такое же поведение (службы внедрения, совместное использование состояния) при программном запуске теста? (по моему примеру)





Итак, чтобы заставить это работать, используйте следующую структуру пакета:
src/main/java/
└── org
└── example
├── cucumberseviceinjection
│ ├── CucumberServiceInjectionApplication.java
│ └── CucumberTestRunner.java
└── glue
├── CucumberTestContext.java
├── DummyService.java
└── StepDefinitions.java
И вам придется настроить тестовый контекст, используя отдельный класс. Не в классе определения шага. Один из способов сделать это может быть такой, но вы можете настроить это и другими способами. Для этого лучше всего прочитать документацию Spring.
@CucumberContextConfiguration
@ContextConfiguration
public class CucumberTestContext {
@Configuration
@ComponentScan(basePackages = "org.example.glue")
public static class TestConfiguration {
}
}
Так почему же это работает?
В этой ситуации есть несколько фреймворков, наложенных друг на друга.
Spring -> JUnit Platform -> Cucumber JUnit Engine -> Cucumber Core -> Cucumber Spring -> Springs Test Context Manager -> Spring
И весна там дважды. Один раз в качестве приложения, запускающего Cucumber, и один раз в качестве контейнера внедрения зависимостей для тестов. И это важно иметь в виду.
Когда вы настраиваете cucumber.glue на платформе JUnit. Затем это передается в Cucumber, который использует его для поиска определений шагов классов (т. е. метода, помеченного @Given, @When, @Then и т. д.).
И для каждого сценария огурец создаст экземпляры всех классов с определениями шагов. Это гарантирует, что каждый тест выполняется изолированно — так же, как, скажем, JUnit. Хотя при использовании Cucumber Spring контекст приложения и все bean-компоненты, не относящиеся к сценарию, разделяются между тестами - так же, как и JUnit. Так что вам следует помнить об этом.
Для создания экземпляров определений шагов и их зависимостей используется Cucumber Spring. Cucumber Spring обертывает Springs TestContextManager framework. Менеджер тестового контекста должен знать, как создать контекст приложения.
Обычно с JUnit вы используете это так:
@SpringBootTest
public class ExampleTest {
...
}
И поскольку @SpringBootTest имеет метааннотацию с
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})
JUnit знает, что он должен использовать SpringExtension, который затем передает ExampleTest менеджеру тестового контекста, который видит аннотацию BootstrapWith с SpringBootTestContextBootstrapper, а затем знает, что ему следует искать аннотированный класс @SpringBootApplication для создания тестового контекста.
Но это JUnit, а не Cucumber. В Cucumber нет тестовых классов. У него есть файлы объектов, и к ним нельзя добавлять аннотации. ;)
Поэтому вместо этого нам нужно пометить класс знаком @CucumberContextConfiguration, чтобы Cucumber Spring знал, какой класс передать менеджеру тестового контекста, чтобы он мог создать контекст тестового приложения. И Cucumber Spring ищет этот класс в пакетах, указанных в cucumber.glue.
Так почему бы не использовать это?
@CucumberContextConfiguration
@SpringBootTest
public class CucumberTestContext {
Потому что @SpringBootTest даст указание менеджеру тестового контекста искать приложение Spring, и оно найдет CucumberServiceInjectionApplication. И, вероятно, еще и потому, что вы не можете одновременно запускать два приложения Spring Boot в одной JVM без особой настройки.
И почему отдельные пакеты
При использовании этого:
@CucumberContextConfiguration
@ContextConfiguration
public class CucumberTestContext {
@Configuration
@ComponentScan(basePackages = "org.example.glue")
public static class TestConfiguration {
}
}
Cucumber знает, что нужно передать CucumberTestContext менеджеру тестового контекста. Менеджер видит, что это @ContextConfiguration класс. И поэтому будет искать внутренние классы, помеченные @Configuration, и выбирать любые явно объявленные конфигурации (т. е. @ContextConfiguration(classes = ExampleConfiguration.class).
Поскольку мы объявили внутренний класс, срабатывает @ComponentScan, и мы не хотим, чтобы он сканировал пакеты хост-приложения.
Так как же мне полностью программно использовать разные контексты приложений?
К сожалению, это невозможно. Единственный вариант — объявить несколько конфигураций контекста приложения, поместить каждую в отдельный пакет и запустить несколько тестов с разными наборами конфигураций.
Например:
Test A:
cucumber.glue=org.example.glue.common,org.example.glue.config.a
Test B:
cucumber.glue=org.example.glue.common,org.example.glue.config.b