请求范围的 bean 在 Spring 测试中无法使用 Cucumber
Request-scoped beans not working in Spring tests with Cucumber
我有一个基于 Spring 4.3.28 的应用程序(即不是 Spring Boot!),我想将我的集成测试迁移到 Cucumber。
我已遵循此 tutorial 并将其改编为普通 Spring。
到目前为止,我编写的测试工作正常(Spring 上下文已初始化等),但一旦涉及到请求范围的 bean,它们就会停止工作:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or processing a
request outside of the originally receiving thread? If you are actually operating
within a web request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
我创建了一个小 sample project
试图重现问题。
有一个名为 AppConfig 的上下文配置 class:
@Configuration
public class AppConfig {
@Bean
@Scope("request“) // when this line is removed, the test succeeds
public ExampleService exampleService() {
return new ExampleService();
}
@Bean("dependency")
@Scope("request") // when this line is removed, the test succeeds
public String dependencyBean() {
return "dependency bean";
}
}
ExampleService 是请求范围的,并获得一个由@Autowired 注入的请求范围的 bean:
public class ExampleService {
@Autowired
@Qualifier("dependency")
String dependencyBean;
public String process() { return "I have a "+dependencyBean; }
}
为了测试,我有一个 Spring-注释的 superclass:
@ContextConfiguration(classes = AppConfig.class)
@CucumberContextConfiguration
@WebAppConfiguration
public class TestBase {
@Autowired
public ExampleService underTest;
}
还有一个简单的 Spring 测试运行得很好:
@RunWith(SpringRunner.class)
public class ExampleServicePlainSpringTest extends TestBase {
@Test
public void whenProcessingDataThenResultShouldBeReturned() {
assertThat(this.underTest.process()).isEqualTo("I have a dependency bean");
}
}
Cucumber 测试由此测试执行 class stub:
@RunWith(Cucumber.class)
public class ExampleServiceCucumberTest extends TestBase {}
实际的黄瓜步骤定义在这里:
public class CucumberStepDefinitions extends TestBase {
private String result;
@When("I process data")
public void iProcessData() {
result = this.underTest.process();
}
@Then("the result should be returned")
public void checkResult() {
assertThat(result).isEqualTo("I have a dependency bean");
}
}
Cucumber 的 .feature 文件位于 src/test/resources 目录中,与步骤定义具有相同的包名 class:
Feature: Example
Scenario: Example service bean returns dependency
When I process data
Then the result should be returned
通常当我遇到“找不到线程绑定请求”错误时,这是因为缺少 @WebAppConfiguration
注释,或者当我尝试将请求范围的 bean 注入非请求范围的 bean 时豆。但这里不是这种情况。
我做错了什么?
我想出了解决办法;更新后的代码位于问题中链接的 github 存储库中。
使用 SpringRunner
时,请求上下文在 ServletTestExecutionListener
中初始化,该 ServletTestExecutionListener
隐式添加到 TestExecutionListener
列表中用于测试。
初始化发生在该侦听器的 beforeTestMethod()
方法中。
然而,正如@M.P.Korsanje 在评论中正确指出的(谢谢!),Cucumber 没有测试方法,所以 beforeTestMethod()
永远不会被执行。
我的解决方案是将 ServletTestExecutionListener
的自定义子类添加为 TestExecutionListener
,将 beforeTestClass()
调用委托给 beforeTestMethod()
:
public class ClassLevelServletTestExecutionListener extends ServletTestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
super.beforeTestMethod(testContext);
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
super.afterTestMethod(testContext);
}
}
并且在 ExampleServiceCucumberTest
中:
@ContextConfiguration(classes = {AppConfig.class})
@CucumberContextConfiguration
@WebAppConfiguration
@TestExecutionListeners(ClassLevelServletTestExecutionListener.class)
// extend the Spring class to get the default TestExecutionListeners
public class TestBase extends AbstractJUnit4SpringContextTests {
@Autowired
public ExampleService underTest;
}
我有一个基于 Spring 4.3.28 的应用程序(即不是 Spring Boot!),我想将我的集成测试迁移到 Cucumber。
我已遵循此 tutorial 并将其改编为普通 Spring。
到目前为止,我编写的测试工作正常(Spring 上下文已初始化等),但一旦涉及到请求范围的 bean,它们就会停止工作:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or processing a
request outside of the originally receiving thread? If you are actually operating
within a web request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
我创建了一个小 sample project 试图重现问题。
有一个名为 AppConfig 的上下文配置 class:
@Configuration
public class AppConfig {
@Bean
@Scope("request“) // when this line is removed, the test succeeds
public ExampleService exampleService() {
return new ExampleService();
}
@Bean("dependency")
@Scope("request") // when this line is removed, the test succeeds
public String dependencyBean() {
return "dependency bean";
}
}
ExampleService 是请求范围的,并获得一个由@Autowired 注入的请求范围的 bean:
public class ExampleService {
@Autowired
@Qualifier("dependency")
String dependencyBean;
public String process() { return "I have a "+dependencyBean; }
}
为了测试,我有一个 Spring-注释的 superclass:
@ContextConfiguration(classes = AppConfig.class)
@CucumberContextConfiguration
@WebAppConfiguration
public class TestBase {
@Autowired
public ExampleService underTest;
}
还有一个简单的 Spring 测试运行得很好:
@RunWith(SpringRunner.class)
public class ExampleServicePlainSpringTest extends TestBase {
@Test
public void whenProcessingDataThenResultShouldBeReturned() {
assertThat(this.underTest.process()).isEqualTo("I have a dependency bean");
}
}
Cucumber 测试由此测试执行 class stub:
@RunWith(Cucumber.class)
public class ExampleServiceCucumberTest extends TestBase {}
实际的黄瓜步骤定义在这里:
public class CucumberStepDefinitions extends TestBase {
private String result;
@When("I process data")
public void iProcessData() {
result = this.underTest.process();
}
@Then("the result should be returned")
public void checkResult() {
assertThat(result).isEqualTo("I have a dependency bean");
}
}
Cucumber 的 .feature 文件位于 src/test/resources 目录中,与步骤定义具有相同的包名 class:
Feature: Example
Scenario: Example service bean returns dependency
When I process data
Then the result should be returned
通常当我遇到“找不到线程绑定请求”错误时,这是因为缺少 @WebAppConfiguration
注释,或者当我尝试将请求范围的 bean 注入非请求范围的 bean 时豆。但这里不是这种情况。
我做错了什么?
我想出了解决办法;更新后的代码位于问题中链接的 github 存储库中。
使用 SpringRunner
时,请求上下文在 ServletTestExecutionListener
中初始化,该 ServletTestExecutionListener
隐式添加到 TestExecutionListener
列表中用于测试。
初始化发生在该侦听器的 beforeTestMethod()
方法中。
然而,正如@M.P.Korsanje 在评论中正确指出的(谢谢!),Cucumber 没有测试方法,所以 beforeTestMethod()
永远不会被执行。
我的解决方案是将 ServletTestExecutionListener
的自定义子类添加为 TestExecutionListener
,将 beforeTestClass()
调用委托给 beforeTestMethod()
:
public class ClassLevelServletTestExecutionListener extends ServletTestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
super.beforeTestMethod(testContext);
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
super.afterTestMethod(testContext);
}
}
并且在 ExampleServiceCucumberTest
中:
@ContextConfiguration(classes = {AppConfig.class})
@CucumberContextConfiguration
@WebAppConfiguration
@TestExecutionListeners(ClassLevelServletTestExecutionListener.class)
// extend the Spring class to get the default TestExecutionListeners
public class TestBase extends AbstractJUnit4SpringContextTests {
@Autowired
public ExampleService underTest;
}